From 21ad26b6fffb93b8c83d517cbd49837d402bab0f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 30 Jun 2017 12:59:34 -0700 Subject: [PATCH 001/144] 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 --- src/harness/unittests/projectErrors.ts | 2 +- src/server/editorServices.ts | 156 +++++++++++++------------ src/server/project.ts | 4 + 3 files changed, 88 insertions(+), 74 deletions(-) diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index f250d4d9b36..142aa3bf1b1 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -94,8 +94,8 @@ namespace ts.projectSystem { checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]); host.reloadFS([file1, file2, config]); + host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created); - projectService.openClientFile(file1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectErrors(projectService.synchronizeProjectList([])[0], []); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3edb880f620..2eb1d73ad78 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -211,20 +211,6 @@ namespace ts.server { extraFileExtensions?: JsFileExtensionInfo[]; } - interface ConfigFileConversionResult { - success: boolean; - configFileErrors?: Diagnostic[]; - - projectOptions?: ProjectOptions; - } - - interface OpenConfigFileResult { - success: boolean; - errors?: Diagnostic[]; - - project?: ConfiguredProject; - } - export interface OpenConfiguredProjectResult { configFileName?: NormalizedPath; 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, - * 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. + * This function tries to search for a tsconfig.json for the given file. + * This is different from the method the compiler uses because + * 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); this.logger.info(`Search path: ${searchPath}`); // check if this file is already included in one of external projects const configFileName = this.findConfigFile(asNormalizedPath(searchPath), projectRootPath); - if (!configFileName) { - this.logger.info("No config files found."); - 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 }; - } + if (configFileName) { + this.logger.info(`Config file name: ${configFileName}`); } 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 @@ -971,7 +959,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string): ConfigFileConversionResult { + private convertConfigFileContentToProjectOptions(configFilename: string) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -996,23 +984,38 @@ namespace ts.server { Debug.assert(!!parsedCommandLine.fileNames); + let success: boolean; + let projectOptions: ProjectOptions; + if (parsedCommandLine.fileNames.length === 0) { 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 = { - 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 }; + return { success, projectOptions, configFileErrors: errors }; } private exceededTotalSizeLimitForNonTsFiles(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader) { @@ -1149,8 +1152,7 @@ namespace ts.server { } private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, 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 scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); @@ -1159,25 +1161,21 @@ namespace ts.server { project.addRoot(info); } else { - (errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFilename)); + (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename)); } } - project.setProjectErrors(concatenate(configFileErrors, errors)); + project.setProjectErrors(configFileErrors); project.setTypeAcquisition(typeAcquisition); project.updateGraph(); } - private openConfigFile(configFileName: NormalizedPath, clientFileName?: string): OpenConfigFileResult { - const conversionResult = this.convertConfigFileContentToProjectOptions(configFileName); - const projectOptions: ProjectOptions = conversionResult.success - ? conversionResult.projectOptions - : { files: [], compilerOptions: {}, configHasExtendsProperty: false, configHasFilesProperty: false, configHasIncludeProperty: false, configHasExcludeProperty: false, typeAcquisition: { enable: false } }; - const project = this.createAndAddConfiguredProject(configFileName, projectOptions, conversionResult.configFileErrors, clientFileName); - return { - success: conversionResult.success, - project, - errors: project.getGlobalProjectErrors() - }; + private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { + const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(configFileName); + if (success) { + this.logger.info(`Opened configuration file ${configFileName}`); + } + + return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, clientFileName); } private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { @@ -1392,6 +1390,8 @@ namespace ts.server { } if (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"); } } @@ -1465,9 +1465,20 @@ namespace ts.server { let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); if (!project) { - ({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName, projectRootPath)); + configFileName = this.getConfigFileNameForFile(fileName, projectRootPath); if (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) { @@ -1792,9 +1803,8 @@ namespace ts.server { for (const tsconfigFile of tsConfigFiles) { let project = this.findConfiguredProjectByProjectName(tsconfigFile); if (!project) { - const result = this.openConfigFile(tsconfigFile); - // TODO: save errors - project = result.success && result.project; + // errors are stored in the project + project = this.openConfigFile(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 diff --git a/src/server/project.ts b/src/server/project.ts index 778f62947ff..74d4f2c907c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -626,6 +626,10 @@ namespace ts.server { for (const missingFilePath of missingFilePaths) { if (!this.missingFilesMap.has(missingFilePath)) { 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)) { fileWatcher.close(); this.missingFilesMap.delete(missingFilePath); From ae33ae894d290092bb36a5a2415d0214092ddc9d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 30 Jun 2017 18:04:33 -0700 Subject: [PATCH 002/144] Restructure updating the configured project from disk and actual project update Also reload the projects when extra extension in the host change --- .../unittests/tsserverProjectSystem.ts | 18 +++-- src/server/editorServices.ts | 75 ++++++++++++------- src/server/project.ts | 4 +- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index b34669c2690..83a9b58a004 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -770,7 +770,7 @@ namespace ts.projectSystem { // remove the tsconfig file host.reloadFS(filesWithoutConfig); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); + host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Deleted); checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); @@ -1837,16 +1837,18 @@ namespace ts.projectSystem { // HTML file will not be included in any projects yet checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); + const configuredProj = projectService.configuredProjects[0]; + checkProjectActualFiles(configuredProj, [file1.path, config.path]); // Specify .html extension as mixed content const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); session.executeCommand(configureHostRequest).response; - // HTML file still not included in the project as it is closed + // The configured project should now be updated to include html file checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); + assert.strictEqual(projectService.configuredProjects[0], configuredProj, "Same configured project should be updated"); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); // Open HTML file projectService.applyChangesInOpenFiles( @@ -2859,7 +2861,7 @@ namespace ts.projectSystem { const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1]); - host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed); + host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; @@ -2911,7 +2913,8 @@ namespace ts.projectSystem { const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1, configFile]); - host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed); + host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted); + host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Created); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; @@ -2919,7 +2922,8 @@ namespace ts.projectSystem { moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1, configFile]); - host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed); + host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Deleted); + host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Created); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2eb1d73ad78..852b4be78d6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -538,21 +538,21 @@ namespace ts.server { } } - private onSourceFileChanged(fileName: NormalizedPath) { + private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) { const info = this.getScriptInfoForNormalizedPath(fileName); if (!info) { this.logger.info(`Error: got watch notification for unknown file: ${fileName}`); return; } - if (!this.host.fileExists(fileName)) { + if (eventKind === FileWatcherEventKind.Deleted) { // File was deleted this.handleDeletedFile(info); } else { - if (info && (!info.isScriptOpen())) { + if (!info.isScriptOpen()) { if (info.containingProjects.length === 0) { - // Orphan script info, remove it as we can always reload it on next open + // Orphan script info, remove it as we can always reload it on next open file request info.stopWatcher(); this.filenameToScriptInfo.delete(info.path); } @@ -605,7 +605,7 @@ namespace ts.server { this.logger.info(`Type root file ${fileName} changed`); this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { project.updateTypes(); - this.updateConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) + this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) this.refreshInferredProjects(); }); } @@ -644,7 +644,7 @@ namespace ts.server { // just update the current project. this.logger.info("Updating configured project"); - this.updateConfiguredProject(project); + this.updateConfiguredProject(project, projectOptions, configFileErrors); // Call refreshInferredProjects to clean up inferred projects we may have // created for the new files @@ -652,11 +652,19 @@ namespace ts.server { } } - private onConfigChangedForConfiguredProject(project: ConfiguredProject) { + private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { const configFileName = project.getConfigFilePath(); - this.logger.info(`Config file changed: ${configFileName}`); - const configFileErrors = this.updateConfiguredProject(project); - this.reportConfigFileDiagnostics(configFileName, configFileErrors, /*triggerFile*/ configFileName); + if (eventKind === FileWatcherEventKind.Deleted) { + this.logger.info(`Config file deleted: ${configFileName}`); + // TODO: (sheetalkamat) Get the list of open files from this project + // and update the projects + this.removeProject(project); + } + else { + this.logger.info(`Config file changed: ${configFileName}`); + this.reloadConfiguredProject(project); + this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); + } this.refreshInferredProjects(); } @@ -670,6 +678,8 @@ namespace ts.server { return; } + // TODO: (sheetalkamat) Technically we shouldnt have to do this: + // We can report these errors in reload projects or after reload project? const { configFileErrors } = this.convertConfigFileContentToProjectOptions(fileName); this.reportConfigFileDiagnostics(fileName, configFileErrors, fileName); @@ -883,7 +893,7 @@ namespace ts.server { this.openConfigFile(configFileName, fileName); } else { - this.updateConfiguredProject(project); + this.reloadConfiguredProject(project); } } } @@ -1133,7 +1143,7 @@ namespace ts.server { this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); - project.watchConfigFile(project => this.onConfigChangedForConfiguredProject(project)); + project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)); if (!sizeLimitExceeded) { this.watchConfigDirectoryForProject(project, projectOptions); } @@ -1161,6 +1171,9 @@ namespace ts.server { project.addRoot(info); } else { + // TODO: (sheetalkamat) because the files are not added as a root, we wont have these available in + // missing files unless someone recreates the project or it was also refrenced in existing sourcefile + // Also these errors wouldnt show correct errors (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename)); } } @@ -1183,12 +1196,11 @@ namespace ts.server { const newRootScriptInfos: ScriptInfo[] = []; const newRootScriptInfoMap: NormalizedPathMap = createNormalizedPathMap(); - let projectErrors: Diagnostic[]; let rootFilesChanged = false; for (const f of newUncheckedFiles) { const newRootFile = propertyReader.getFileName(f); if (!this.host.fileExists(newRootFile)) { - (projectErrors || (projectErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); + (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); continue; } const normalizedPath = toNormalizedPath(newRootFile); @@ -1247,17 +1259,20 @@ namespace ts.server { if (compileOnSave !== undefined) { project.compileOnSaveEnabled = compileOnSave; } - project.setProjectErrors(concatenate(configFileErrors, projectErrors)); + project.setProjectErrors(configFileErrors); project.updateGraph(); } - private updateConfiguredProject(project: ConfiguredProject) { - if (!this.host.fileExists(project.getConfigFilePath())) { - this.logger.info("Config file deleted"); - this.removeProject(project); - return; - } + /** + * Read the config file of the project again and update the project + * @param project + */ + private reloadConfiguredProject(project: ConfiguredProject) { + // At this point, there is no reason to not have configFile in the host + + // TODO: (sheetalkamat) configErrors should always be in project and there shouldnt be + // any need to return these errors // 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 @@ -1266,9 +1281,16 @@ namespace ts.server { if (!success) { // reset project settings to default this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors); - return configFileErrors; } + return this.updateConfiguredProject(project, projectOptions, configFileErrors); + } + + /** + * Updates the configured project with updated config file contents + * @param project + */ + private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) { if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { project.setCompilerOptions(projectOptions.compilerOptions); if (!project.languageServiceEnabled) { @@ -1277,13 +1299,13 @@ namespace ts.server { } project.disableLanguageService(); project.stopWatchingDirectory(); + project.setProjectErrors(configFileErrors); } else { project.enableLanguageService(); this.watchConfigDirectoryForProject(project, projectOptions); this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } - return configFileErrors; } createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) { @@ -1324,7 +1346,7 @@ namespace ts.server { // 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, _ => this.onSourceFileChanged(fileName))); + info.setWatcher(this.host.watchFile(fileName, (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind))); } } @@ -1390,8 +1412,9 @@ namespace ts.server { } if (args.extraFileExtensions) { this.hostConfiguration.extraFileExtensions = args.extraFileExtensions; - // TODO: (sheetalkamat) We need to update the projects because of we might interprete more/less files + // 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.reloadProjects(); this.logger.info("Host file extension mappings updated"); } } @@ -1408,6 +1431,8 @@ namespace ts.server { this.logger.info("reload projects."); // try to reload config file for all open files for (const info of this.openFiles) { + // TODO: (sheetalkamat) batch these = multiple open files from the same project will + // end up updating project that many times this.openOrUpdateConfiguredProjectForFile(info.fileName); } this.refreshInferredProjects(); diff --git a/src/server/project.ts b/src/server/project.ts index 74d4f2c907c..6788081176c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1072,8 +1072,8 @@ namespace ts.server { })); } - watchConfigFile(callback: (project: ConfiguredProject) => void) { - this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), _ => callback(this)); + watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) { + this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); } watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { From 75698a893f4ba7a0c3399a9082b484ff12e06767 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 5 Jul 2017 18:03:11 -0700 Subject: [PATCH 003/144] Add project roots in the list of root files even if they arent present on the disk This helps in reporting errors as well as syncing of the configured/external project when the files are created --- src/harness/unittests/projectErrors.ts | 89 ++++++++++------- .../unittests/tsserverProjectSystem.ts | 37 ++++++- src/server/editorServices.ts | 97 +++++++++---------- src/server/project.ts | 45 +++++++-- src/server/scriptInfo.ts | 8 +- src/services/services.ts | 10 +- 6 files changed, 179 insertions(+), 107 deletions(-) diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 142aa3bf1b1..7c694ec4296 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -20,19 +20,37 @@ namespace ts.projectSystem { } } + function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { + assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); + if (expectedErrors.length) { + for (let i = 0; i < errors.length; i++) { + const actualMessage = errors[i].message; + const expectedMessage = expectedErrors[i]; + assert.isTrue(actualMessage.indexOf(errors[i].message) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + } + } + } + it("external project - diagnostics for missing files", () => { const file1 = { path: "/a/b/app.ts", content: "" }; const file2 = { - path: "/a/b/lib.ts", + path: "/a/b/applib.ts", content: "" }; // only file1 exists - expect error - const host = createServerHost([file1]); - const projectService = createProjectService(host); + const host = createServerHost([file1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); const projectFileName = "/a/b/test.csproj"; + const compilerOptionsRequest = { + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName } + }; { projectService.openExternalProject({ @@ -41,35 +59,27 @@ namespace ts.projectSystem { rootFiles: toExternalFiles([file1.path, file2.path]) }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - const knownProjects = projectService.synchronizeProjectList([]); - checkProjectErrors(knownProjects[0], ["File '/a/b/lib.ts' not found."]); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); } // only file2 exists - expect error - host.reloadFS([file2]); + host.reloadFS([file2, libFile]); { - projectService.openExternalProject({ - projectFileName, - options: {}, - rootFiles: toExternalFiles([file1.path, file2.path]) - }); - projectService.checkNumberOfProjects({ externalProjects: 1 }); - const knownProjects = projectService.synchronizeProjectList([]); - checkProjectErrors(knownProjects[0], ["File '/a/b/app.ts' not found."]); + host.triggerFileWatcherCallback(file1.path, FileWatcherEventKind.Deleted); + host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); } // both files exist - expect no errors - host.reloadFS([file1, file2]); + host.reloadFS([file1, file2, libFile]); { - projectService.openExternalProject({ - projectFileName, - options: {}, - rootFiles: toExternalFiles([file1.path, file2.path]) - }); - - projectService.checkNumberOfProjects({ externalProjects: 1 }); - const knownProjects = projectService.synchronizeProjectList([]); - checkProjectErrors(knownProjects[0], []); + host.triggerFileWatcherCallback(file1.path, FileWatcherEventKind.Created); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const diags = session.executeCommand(compilerOptionsRequest).response; + checkDiagnosticsWithLinePos(diags, []); } }); @@ -79,25 +89,34 @@ namespace ts.projectSystem { content: "" }; const file2 = { - path: "/a/b/lib.ts", + path: "/a/b/applib.ts", content: "" }; const config = { path: "/a/b/tsconfig.json", content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) }) }; - const host = createServerHost([file1, config]); - const projectService = createProjectService(host); + const host = createServerHost([file1, config, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + openFilesForSession([file1], session); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects[0]; + const compilerOptionsRequest = { + type: "request", + command: server.CommandNames.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: project.getProjectName() } + }; + let diags = session.executeCommand(compilerOptionsRequest).response; + checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); - projectService.openClientFile(file1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]); - - host.reloadFS([file1, file2, config]); + host.reloadFS([file1, file2, config, libFile]); host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectErrors(projectService.synchronizeProjectList([])[0], []); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + diags = session.executeCommand(compilerOptionsRequest).response; + checkDiagnosticsWithLinePos(diags, []); }); it("configured projects - diagnostics for corrupted config 1", () => { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 83a9b58a004..6ffc636dc7f 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -907,6 +907,41 @@ namespace ts.projectSystem { checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); + it("handles the missing files - that were added to program because they were added with /// { + const file1: FileOrFolder = { + path: "/a/b/commonFile1.ts", + content: `/// + let x = y` + }; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + checkNumberOfInferredProjects(projectService, 1); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path]); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); + let diags = session.executeCommand(getErrRequest).response; + + // Two errors: CommonFile2 not found and cannot find name y + assert.equal(diags.length, 2, diags.map(diag => flattenDiagnosticMessageText(diag.text, "\n")).join("\n")); + + host.reloadFS([file1, commonFile2, libFile]); + host.triggerFileWatcherCallback(commonFile2.path, FileWatcherEventKind.Created); + host.runQueuedTimeoutCallbacks(); + checkNumberOfInferredProjects(projectService, 1); + assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); + diags = session.executeCommand(getErrRequest).response; + assert.equal(diags.length, 0); + }); + it("should create new inferred projects for files excluded from a configured project", () => { const configFile: FileOrFolder = { path: "/a/b/tsconfig.json", @@ -2869,7 +2904,7 @@ namespace ts.projectSystem { moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1]); - host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed); + host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Deleted); host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 852b4be78d6..7d43a1e67a6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -245,10 +245,6 @@ namespace ts.server { } } - function createFileNotFoundDiagnostic(fileName: string) { - return createCompilerDiagnostic(Diagnostics.File_0_not_found, fileName); - } - /** * TODO: enforce invariants: * - script info can be never migrate to state - root file in inferred project, this is only a starting point @@ -1166,15 +1162,14 @@ namespace ts.server { const rootFilename = propertyReader.getFileName(f); const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); + const fileName = toNormalizedPath(rootFilename); if (this.host.fileExists(rootFilename)) { - const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); + const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); project.addRoot(info); } else { - // TODO: (sheetalkamat) because the files are not added as a root, we wont have these available in - // missing files unless someone recreates the project or it was also refrenced in existing sourcefile - // Also these errors wouldnt show correct errors - (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename)); + // Create the file root with just the filename so that LS will have correct set of roots + project.addMissingFileRoot(fileName); } } project.setProjectErrors(configFileErrors); @@ -1192,67 +1187,63 @@ namespace ts.server { } private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { - const oldRootScriptInfos = project.getRootScriptInfos(); - const newRootScriptInfos: ScriptInfo[] = []; - const newRootScriptInfoMap: NormalizedPathMap = createNormalizedPathMap(); + const projectRootFilesMap = project.getRootFilesMap(); + const newRootScriptInfoMap: Map = createMap(); - let rootFilesChanged = false; for (const f of newUncheckedFiles) { const newRootFile = propertyReader.getFileName(f); - if (!this.host.fileExists(newRootFile)) { - (configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(newRootFile)); - continue; - } const normalizedPath = toNormalizedPath(newRootFile); - let scriptInfo = this.getScriptInfoForNormalizedPath(normalizedPath); - if (!scriptInfo || !project.isRoot(scriptInfo)) { - rootFilesChanged = true; - if (!scriptInfo) { - const scriptKind = propertyReader.getScriptKind(f); - const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent); + let scriptInfo: ScriptInfo | NormalizedPath; + let path: Path; + if (!this.host.fileExists(newRootFile)) { + path = normalizedPathToPath(normalizedPath, this.host.getCurrentDirectory(), this.toCanonicalFileName); + const existingValue = projectRootFilesMap.get(path); + if (isScriptInfo(existingValue)) { + project.removeFile(existingValue); + projectRootFilesMap.set(path, normalizedPath); } + scriptInfo = normalizedPath; } - newRootScriptInfos.push(scriptInfo); - newRootScriptInfoMap.set(scriptInfo.fileName, scriptInfo); - } - - if (rootFilesChanged || newRootScriptInfos.length !== oldRootScriptInfos.length) { - let toAdd: ScriptInfo[]; - let toRemove: ScriptInfo[]; - for (const oldFile of oldRootScriptInfos) { - if (!newRootScriptInfoMap.contains(oldFile.fileName)) { - (toRemove || (toRemove = [])).push(oldFile); - } - } - for (const newFile of newRootScriptInfos) { - if (!project.isRoot(newFile)) { - (toAdd || (toAdd = [])).push(newFile); - } - } - if (toRemove) { - for (const f of toRemove) { - project.removeFile(f); - } - } - if (toAdd) { - for (const f of toAdd) { - if (f.isScriptOpen() && isRootFileInInferredProject(f)) { + else { + const scriptKind = propertyReader.getScriptKind(f); + const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent); + path = scriptInfo.path; + // If this script info is not already a root add it + if (!project.isRoot(scriptInfo)) { + if (scriptInfo.isScriptOpen() && isRootFileInInferredProject(scriptInfo)) { // if file is already root in some inferred project // - remove the file from that project and delete the project if necessary - const inferredProject = f.containingProjects[0]; - inferredProject.removeFile(f); + const inferredProject = scriptInfo.containingProjects[0]; + inferredProject.removeFile(scriptInfo); if (!inferredProject.hasRoots()) { this.removeProject(inferredProject); } } - project.addRoot(f); + project.addRoot(scriptInfo); } } + newRootScriptInfoMap.set(path, scriptInfo); + } + + // project's root file map size is always going to be larger than new roots map + // as we have already all the new files to the project + if (projectRootFilesMap.size > newRootScriptInfoMap.size) { + projectRootFilesMap.forEach((value, path) => { + if (!newRootScriptInfoMap.has(path)) { + if (isScriptInfo(value)) { + project.removeFile(value); + } + else { + projectRootFilesMap.delete(path); + project.markAsDirty(); + } + } + }); } project.setCompilerOptions(newOptions); - (project).setTypeAcquisition(newTypeAcquisition); + project.setTypeAcquisition(newTypeAcquisition); // VS only set the CompileOnSaveEnabled option in the request if the option was changed recently // therefore if it is undefined, it should not be updated. diff --git a/src/server/project.ts b/src/server/project.ts index 6788081176c..7e94de2afac 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -102,9 +102,19 @@ namespace ts.server { (mod: { typescript: typeof ts }): PluginModule; } + /** + * The project root can be script info - if root is present, + * or it could be just normalized path if root wasnt present on the host(only for non inferred project) + */ + export type ProjectRoot = ScriptInfo | NormalizedPath; + /* @internal */ + export function isScriptInfo(value: ProjectRoot): value is ScriptInfo { + return value instanceof ScriptInfo; + } + export abstract class Project { private rootFiles: ScriptInfo[] = []; - private rootFilesMap: Map = createMap(); + private rootFilesMap: Map = createMap(); private program: ts.Program; private externalFiles: SortedReadonlyArray; private missingFilesMap: Map = createMap(); @@ -335,12 +345,13 @@ namespace ts.server { getRootFilesLSHost() { const result: string[] = []; if (this.rootFiles) { - for (const f of this.rootFiles) { - if (this.languageServiceEnabled || f.isScriptOpen()) { + this.rootFilesMap.forEach((value, _path) => { + const f: ScriptInfo = isScriptInfo(value) && value; + if (this.languageServiceEnabled || (f && f.isScriptOpen())) { // if language service is disabled - process only files that are open - result.push(f.fileName); + result.push(f ? f.fileName : value as NormalizedPath); } - } + }); if (this.typingFiles) { for (const f of this.typingFiles) { result.push(f); @@ -350,6 +361,10 @@ namespace ts.server { return result; } + getRootFilesMap() { + return this.rootFilesMap; + } + getRootScriptInfos() { return this.rootFiles; } @@ -458,7 +473,7 @@ namespace ts.server { } isRoot(info: ScriptInfo) { - return this.rootFilesMap && this.rootFilesMap.has(info.path); + return this.rootFilesMap && this.rootFilesMap.get(info.path) === info; } // add a root file to project @@ -472,6 +487,14 @@ namespace ts.server { } } + // add a root file to project + addMissingFileRoot(fileName: NormalizedPath) { + const path = toPath(fileName, this.projectService.host.getCurrentDirectory(), + createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames)); + this.rootFilesMap.set(path, fileName); + this.markAsDirty(); + } + removeFile(info: ScriptInfo, detachFromProject = true) { if (this.isRoot(info)) { this.removeRoot(info); @@ -671,6 +694,12 @@ namespace ts.server { getScriptInfoLSHost(fileName: string) { const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false); if (scriptInfo) { + const existingValue = this.rootFilesMap.get(scriptInfo.path); + if (existingValue !== undefined && existingValue !== scriptInfo) { + // This was missing path earlier but now the file exists. Update the root + this.rootFiles.push(scriptInfo); + this.rootFilesMap.set(scriptInfo.path, scriptInfo); + } scriptInfo.attachToProject(this); } return scriptInfo; @@ -898,12 +927,12 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { + super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { - if (filter(this.getRootScriptInfos(), info => info.isJavaScript()).length === 0) { + if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { this.toggleJsInferredProject(/*isJsInferredProject*/ false); } } - super.removeRoot(info); } getProjectRootPath() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 534d73a6410..ee5e45c7c47 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -254,8 +254,14 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { + const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways p.removeFile(this, /*detachFromProjects*/ false); + // If the info was for the external or configured project's root, + // add missing file as the root + if (isInfoRoot && p.projectKind !== ProjectKind.Inferred) { + p.addMissingFileRoot(this.fileName); + } } this.containingProjects.length = 0; } @@ -376,4 +382,4 @@ namespace ts.server { return this.scriptKind === ScriptKind.JS || this.scriptKind === ScriptKind.JSX; } } -} \ No newline at end of file +} diff --git a/src/services/services.ts b/src/services/services.ts index 759de900b84..28a29352b80 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -862,15 +862,7 @@ namespace ts { } public getRootFileNames(): string[] { - const fileNames: string[] = []; - - this.fileNameToEntry.forEach(value => { - if (value) { - fileNames.push(value.hostFileName); - } - }); - - return fileNames; + return this.host.getScriptFileNames(); } public getVersion(path: Path): string { From f15491046c2a3d5597546953d424fa34affc8fcb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Jul 2017 17:50:59 -0700 Subject: [PATCH 004/144] Move the server file system to handle watches on file/folder create, update and delete This will ensure that the calling of watches doesnt rely on writing test correctly --- .../unittests/tsserverProjectSystem.ts | 326 +++++++++++++----- src/harness/unittests/typingsInstaller.ts | 2 +- 2 files changed, 236 insertions(+), 92 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 6ffc636dc7f..fda10cae6c9 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -254,29 +254,29 @@ namespace ts.projectSystem { } export function isFolder(s: FSEntry): s is Folder { - return isArray((s).entries); + return s && isArray((s).entries); } export function isFile(s: FSEntry): s is File { - return typeof (s).content === "string"; + return s && typeof (s).content === "string"; } - export function addFolder(fullPath: string, toPath: (s: string) => Path, fs: Map): Folder { - const path = toPath(fullPath); - if (fs.has(path)) { - Debug.assert(isFolder(fs.get(path))); - return (fs.get(path)); + function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], fileName: string) { + if (callbacks) { + const cbs = callbacks.slice(); + for (const cb of cbs) { + cb(fileName); + } } + } - const entry: Folder = { path, entries: [], fullPath }; - fs.set(path, entry); - - const baseFullPath = getDirectoryPath(fullPath); - if (fullPath !== baseFullPath) { - addFolder(baseFullPath, toPath, fs).entries.push(entry); + function invokeFileWatcher(callbacks: FileWatcherCallback[], fileName: string, eventId: FileWatcherEventKind) { + if (callbacks) { + const cbs = callbacks.slice(); + for (const cb of cbs) { + cb(fileName, eventId); + } } - - return entry; } export function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { @@ -315,8 +315,8 @@ namespace ts.projectSystem { checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); } - export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[]) { - checkMapKeys("watchedDirectories", host.watchedDirectories, expectedDirectories); + export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) { + checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); } export function checkProjectActualFiles(project: server.Project, expectedFiles: string[]) { @@ -370,17 +370,16 @@ namespace ts.projectSystem { private readonly output: string[] = []; - private fs: Map; + private fs: Map = createMap(); private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private timeoutCallbacks = new Callbacks(); private immediateCallbacks = new Callbacks(); - readonly watchedDirectories = createMultiMap<{ cb: DirectoryWatcherCallback, recursive: boolean }>(); + readonly watchedDirectories = createMultiMap(); + readonly watchedDirectoriesRecursive = createMultiMap(); readonly watchedFiles = createMultiMap(); - private filesOrFolders: FileOrFolder[]; - constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName); @@ -388,58 +387,201 @@ namespace ts.projectSystem { this.reloadFS(fileOrFolderList); } - reloadFS(filesOrFolders: FileOrFolder[]) { - this.filesOrFolders = filesOrFolders; - this.fs = createMap(); + private toFullPath(s: string) { + const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); + return this.toPath(fullPath); + } + + reloadFS(fileOrFolderList: FileOrFolder[]) { + const mapNewLeaves = createMap(); + const isNewFs = this.fs.size === 0; // always inject safelist file in the list of files - for (const fileOrFolder of filesOrFolders.concat(safeList)) { - const path = this.toPath(fileOrFolder.path); - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); - if (typeof fileOrFolder.content === "string") { - const entry = { path, content: fileOrFolder.content, fullPath, fileSize: fileOrFolder.fileSize }; - this.fs.set(path, entry); - addFolder(getDirectoryPath(fullPath), this.toPath, this.fs).entries.push(entry); + for (const fileOrFolder of fileOrFolderList.concat(safeList)) { + const path = this.toFullPath(fileOrFolder.path); + mapNewLeaves.set(path, true); + // If its a change + const currentEntry = this.fs.get(path); + if (currentEntry) { + if (isFile(currentEntry)) { + if (typeof fileOrFolder.content === "string") { + // Update file + if (currentEntry.content !== fileOrFolder.content) { + currentEntry.content = fileOrFolder.content; + this.invokeFileWatcher(currentEntry.path, FileWatcherEventKind.Changed); + } + } + else { + // TODO: Changing from file => folder + } + } + else { + // Folder + if (typeof fileOrFolder.content === "string") { + // TODO: Changing from folder => file + } + else { + // Folder update: Nothing to do. + } + } } else { - addFolder(fullPath, this.toPath, this.fs); + this.ensureFileOrFolder(fileOrFolder); + } + } + + if (!isNewFs) { + this.fs.forEach((fileOrFolder, path) => { + // If this entry is not from the new file or folder + if (!mapNewLeaves.get(path)) { + // Leaf entries that arent in new list => remove these + if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) { + this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path)); + } + } + }); + } + } + + ensureFileOrFolder(fileOrFolder: FileOrFolder) { + if (typeof fileOrFolder.content === "string") { + const file = this.toFile(fileOrFolder); + Debug.assert(!this.fs.get(file.path)); + const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); + this.addFileOrFolderInFolder(baseFolder, file); + } + else { + const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + this.ensureFolder(fullPath); + } + } + + private ensureFolder(fullPath: string): Folder { + const path = this.toPath(fullPath); + let folder = this.fs.get(path) as Folder; + if (!folder) { + folder = this.toFolder(fullPath); + const baseFullPath = getDirectoryPath(fullPath); + if (fullPath !== baseFullPath) { + // Add folder in the base folder + const baseFolder = this.ensureFolder(baseFullPath); + this.addFileOrFolderInFolder(baseFolder, folder); + } + else { + // root folder + Debug.assert(this.fs.size === 0); + this.fs.set(path, folder); + } + } + Debug.assert(isFolder(folder)); + return folder; + } + + private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) { + folder.entries.push(fileOrFolder); + this.fs.set(fileOrFolder.path, fileOrFolder); + + if (isFile(fileOrFolder)) { + this.invokeFileWatcher(fileOrFolder.path, FileWatcherEventKind.Created); + } + this.invokeDirectoryWatcher(folder.path, fileOrFolder.path); + } + + private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { + const basePath = getDirectoryPath(fileOrFolder.path); + const baseFolder = this.fs.get(basePath) as Folder; + if (basePath !== fileOrFolder.path) { + Debug.assert(!!baseFolder); + filterMutate(baseFolder.entries, entry => entry !== fileOrFolder); + } + this.fs.delete(fileOrFolder.path); + + if (isFile(fileOrFolder)) { + this.invokeFileWatcher(fileOrFolder.path, FileWatcherEventKind.Deleted); + } + else { + Debug.assert(fileOrFolder.entries.length === 0); + invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), fileOrFolder.path); + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), fileOrFolder.path); + } + + if (basePath !== fileOrFolder.path) { + if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) { + this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); + } + else { + this.invokeRecursiveDirectoryWatcher(baseFolder.path, fileOrFolder.path); } } } + private invokeFileWatcher(filePath: Path, eventId: FileWatcherEventKind) { + const callbacks = this.watchedFiles.get(filePath); + invokeFileWatcher(callbacks, filePath, eventId); + } + + private invokeDirectoryWatcher(folderPath: Path, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectories.get(folderPath), fileName); + this.invokeRecursiveDirectoryWatcher(folderPath, fileName); + } + + private invokeRecursiveDirectoryWatcher(path: Path, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(path), fileName); + const basePath = getDirectoryPath(path); + if (path !== basePath) { + this.invokeRecursiveDirectoryWatcher(basePath, fileName); + } + } + + private toFile(fileOrFolder: FileOrFolder): File { + const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + return { + path: this.toPath(fullPath), + content: fileOrFolder.content, + fullPath, + fileSize: fileOrFolder.fileSize + }; + } + + private toFolder(path: string): Folder { + const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory); + return { + path: this.toPath(fullPath), + entries: [], + fullPath + }; + } + fileExists(s: string) { - const path = this.toPath(s); - return this.fs.has(path) && isFile(this.fs.get(path)); + const path = this.toFullPath(s); + return isFile(this.fs.get(path)); } getFileSize(s: string) { - const path = this.toPath(s); - if (this.fs.has(path)) { - const entry = this.fs.get(path); - if (isFile(entry)) { - return entry.fileSize ? entry.fileSize : entry.content.length; - } + const path = this.toFullPath(s); + const entry = this.fs.get(path); + if (isFile(entry)) { + return entry.fileSize ? entry.fileSize : entry.content.length; } return undefined; } directoryExists(s: string) { - const path = this.toPath(s); - return this.fs.has(path) && isFolder(this.fs.get(path)); + const path = this.toFullPath(s); + return isFolder(this.fs.get(path)); } getDirectories(s: string) { - const path = this.toPath(s); - if (!this.fs.has(path)) { - return []; - } - else { - const entry = this.fs.get(path); - return isFolder(entry) ? map(entry.entries, x => getBaseFileName(x.fullPath)) : []; + const path = this.toFullPath(s); + const folder = this.fs.get(path); + if (isFolder(folder)) { + return map(folder.entries, x => getBaseFileName(x.fullPath)); } + Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); + return []; } readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[], depth?: number): string[] { - return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { + return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const result: FileSystemEntries = { directories: [], files: [] @@ -453,6 +595,9 @@ namespace ts.projectSystem { else if (isFile(entry)) { result.files.push(entry.fullPath); } + else { + Debug.fail("Unknown entry"); + } }); } return result; @@ -460,13 +605,13 @@ namespace ts.projectSystem { } watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { - const path = this.toPath(directoryName); - const cbWithRecursive = { cb: callback, recursive }; - this.watchedDirectories.add(path, cbWithRecursive); + const path = this.toFullPath(directoryName); + const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; + map.add(path, callback); return { referenceCount: 0, directoryName, - close: () => this.watchedDirectories.remove(path, cbWithRecursive) + close: () => map.remove(path, callback) }; } @@ -474,28 +619,28 @@ namespace ts.projectSystem { return Harness.LanguageService.mockHash(s); } - triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void { - const path = this.toPath(directoryName); - const callbacks = this.watchedDirectories.get(path); - if (callbacks) { - for (const callback of callbacks) { - callback.cb(fileName); - } - } + triggerDirectoryWatcherCallback(_directoryName: string, _fileName: string): void { + //const path = this.toPath(directoryName); + //const callbacks = this.watchedDirectories.get(path); + //if (callbacks) { + // for (const callback of callbacks) { + // callback.cb(fileName); + // } + //} } - triggerFileWatcherCallback(fileName: string, eventKind: FileWatcherEventKind): void { - const path = this.toPath(fileName); - const callbacks = this.watchedFiles.get(path); - if (callbacks) { - for (const callback of callbacks) { - callback(path, eventKind); - } - } + triggerFileWatcherCallback(_fileName: string, _eventKind: FileWatcherEventKind): void { + //const path = this.toPath(fileName); + //const callbacks = this.watchedFiles.get(path); + //if (callbacks) { + // for (const callback of callbacks) { + // callback(path, eventKind); + // } + //} } watchFile(fileName: string, callback: FileWatcherCallback) { - const path = this.toPath(fileName); + const path = this.toFullPath(fileName); this.watchedFiles.add(path, callback); return { close: () => this.watchedFiles.remove(path, callback) }; } @@ -531,27 +676,26 @@ namespace ts.projectSystem { } createDirectory(directoryName: string): void { - this.createFileOrFolder({ path: directoryName }); + const folder = this.toFolder(directoryName); + + // base folder has to be present + const base = getDirectoryPath(folder.fullPath); + const baseFolder = this.fs.get(base) as Folder; + Debug.assert(isFolder(baseFolder)); + + Debug.assert(!this.fs.get(folder.path), isFile(this.fs.get(folder.path)) ? `Found the file ${folder.path}` : `Found the folder ${folder.path}`); + this.addFileOrFolderInFolder(baseFolder, folder); } writeFile(path: string, content: string): void { - this.createFileOrFolder({ path, content, fileSize: content.length }); - } + const file = this.toFile({ path, content }); - createFileOrFolder(f: FileOrFolder, createParentDirectory = false): void { - const base = getDirectoryPath(f.path); - if (base !== f.path && !this.directoryExists(base)) { - if (createParentDirectory) { - // TODO: avoid reloading FS on every creation - this.createFileOrFolder({ path: base }, createParentDirectory); - } - else { - throw new Error(`directory ${base} does not exist`); - } - } - const filesOrFolders = this.filesOrFolders.slice(0); - filesOrFolders.push(f); - this.reloadFS(filesOrFolders); + // base folder has to be present + const base = getDirectoryPath(file.fullPath); + const folder = this.fs.get(base) as Folder; + Debug.assert(isFolder(folder)); + + this.addFileOrFolderInFolder(folder, file); } write(message: string) { @@ -566,7 +710,7 @@ namespace ts.projectSystem { this.output.length = 0; } - readonly readFile = (s: string) => (this.fs.get(this.toPath(s))).content; + readonly readFile = (s: string) => (this.fs.get(this.toFullPath(s))).content; readonly resolvePath = (s: string) => s; readonly getExecutingFilePath = () => this.executingFilePath; readonly getCurrentDirectory = () => this.currentDirectory; @@ -738,7 +882,7 @@ namespace ts.projectSystem { checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - checkWatchedDirectories(host, [getDirectoryPath(configFile.path)]); + checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); }); it("add and then remove a config file in a folder with loose files", () => { @@ -785,7 +929,7 @@ namespace ts.projectSystem { const host = createServerHost([commonFile1, libFile, configFile]); const projectService = createProjectService(host); projectService.openClientFile(commonFile1.path); - checkWatchedDirectories(host, ["/a/b"]); + checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index c23e1529984..8c201c84a13 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -38,7 +38,7 @@ namespace ts.projectSystem { function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[] | string, typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void { self.addPostExecAction(installedTypings, success => { for (const file of typingFiles) { - host.createFileOrFolder(file, /*createParentDirectory*/ true); + host.ensureFileOrFolder(file); } cb(success); }); From 0e4436774d79875d20d04a3deb3232e1ba23e906 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Jul 2017 18:11:00 -0700 Subject: [PATCH 005/144] Remove the functions to trigger watch callbacks now that it is auto on reloading FS --- src/harness/unittests/compileOnSave.ts | 6 +- src/harness/unittests/projectErrors.ts | 6 -- .../unittests/tsserverProjectSystem.ts | 55 ------------------- src/harness/unittests/typingsInstaller.ts | 1 - 4 files changed, 1 insertion(+), 67 deletions(-) diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 79e1f921dcb..184e4804cb8 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -208,7 +208,6 @@ namespace ts.projectSystem { file1Consumer1.content = `let y = 10;`; host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - host.triggerFileWatcherCallback(file1Consumer1.path, FileWatcherEventKind.Changed); session.executeCommand(changeModuleFile1ShapeRequest1); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]); @@ -225,7 +224,6 @@ namespace ts.projectSystem { session.executeCommand(changeModuleFile1ShapeRequest1); // Delete file1Consumer2 host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]); - host.triggerFileWatcherCallback(file1Consumer2.path, FileWatcherEventKind.Deleted); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); }); @@ -242,7 +240,6 @@ namespace ts.projectSystem { content: `import {Foo} from "./moduleFile1"; let y = Foo();` }; host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]); - host.triggerDirectoryWatcherCallback(ts.getDirectoryPath(file1Consumer3.path), file1Consumer3.path); host.runQueuedTimeoutCallbacks(); session.executeCommand(changeModuleFile1ShapeRequest1); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]); @@ -475,7 +472,6 @@ namespace ts.projectSystem { openFilesForSession([referenceFile1], session); host.reloadFS([referenceFile1, configFile]); - host.triggerFileWatcherCallback(moduleFile1.path, FileWatcherEventKind.Deleted); const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); sendAffectedFileRequestAndCheckResult(session, request, [ @@ -601,4 +597,4 @@ namespace ts.projectSystem { assert.isTrue(outFileContent.indexOf(file3.content) === -1); }); }); -} \ No newline at end of file +} diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 7c694ec4296..3d093300cbc 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -66,8 +66,6 @@ namespace ts.projectSystem { // only file2 exists - expect error host.reloadFS([file2, libFile]); { - host.triggerFileWatcherCallback(file1.path, FileWatcherEventKind.Deleted); - host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created); checkNumberOfProjects(projectService, { externalProjects: 1 }); const diags = session.executeCommand(compilerOptionsRequest).response; checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); @@ -76,7 +74,6 @@ namespace ts.projectSystem { // both files exist - expect no errors host.reloadFS([file1, file2, libFile]); { - host.triggerFileWatcherCallback(file1.path, FileWatcherEventKind.Created); checkNumberOfProjects(projectService, { externalProjects: 1 }); const diags = session.executeCommand(compilerOptionsRequest).response; checkDiagnosticsWithLinePos(diags, []); @@ -112,7 +109,6 @@ namespace ts.projectSystem { checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); host.reloadFS([file1, file2, config, libFile]); - host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created); checkNumberOfProjects(projectService, { configuredProjects: 1 }); diags = session.executeCommand(compilerOptionsRequest).response; @@ -154,7 +150,6 @@ namespace ts.projectSystem { } // fix config and trigger watcher host.reloadFS([file1, file2, correctConfig]); - host.triggerFileWatcherCallback(correctConfig.path, FileWatcherEventKind.Changed); { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); @@ -196,7 +191,6 @@ namespace ts.projectSystem { } // break config and trigger watcher host.reloadFS([file1, file2, corruptedConfig]); - host.triggerFileWatcherCallback(corruptedConfig.path, FileWatcherEventKind.Changed); { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fda10cae6c9..51eea84f49b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -619,26 +619,6 @@ namespace ts.projectSystem { return Harness.LanguageService.mockHash(s); } - triggerDirectoryWatcherCallback(_directoryName: string, _fileName: string): void { - //const path = this.toPath(directoryName); - //const callbacks = this.watchedDirectories.get(path); - //if (callbacks) { - // for (const callback of callbacks) { - // callback.cb(fileName); - // } - //} - } - - triggerFileWatcherCallback(_fileName: string, _eventKind: FileWatcherEventKind): void { - //const path = this.toPath(fileName); - //const callbacks = this.watchedFiles.get(path); - //if (callbacks) { - // for (const callback of callbacks) { - // callback(path, eventKind); - // } - //} - } - watchFile(fileName: string, callback: FileWatcherCallback) { const path = this.toFullPath(fileName); this.watchedFiles.add(path, callback); @@ -905,7 +885,6 @@ namespace ts.projectSystem { // Add a tsconfig file host.reloadFS(filesWithConfig); - host.triggerDirectoryWatcherCallback("/a/b", configFile.path); checkNumberOfInferredProjects(projectService, 1); checkNumberOfConfiguredProjects(projectService, 1); @@ -914,7 +893,6 @@ namespace ts.projectSystem { // remove the tsconfig file host.reloadFS(filesWithoutConfig); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Deleted); checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); @@ -937,7 +915,6 @@ namespace ts.projectSystem { // add a new ts file host.reloadFS([commonFile1, commonFile2, libFile, configFile]); - host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); @@ -1040,13 +1017,11 @@ namespace ts.projectSystem { // delete commonFile2 host.reloadFS([commonFile1, configFile]); - host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); checkProjectRootFiles(project, [commonFile1.path]); // re-add commonFile2 host.reloadFS([commonFile1, commonFile2, configFile]); - host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); @@ -1076,7 +1051,6 @@ namespace ts.projectSystem { assert.equal(diags.length, 2, diags.map(diag => flattenDiagnosticMessageText(diag.text, "\n")).join("\n")); host.reloadFS([file1, commonFile2, libFile]); - host.triggerFileWatcherCallback(commonFile2.path, FileWatcherEventKind.Created); host.runQueuedTimeoutCallbacks(); checkNumberOfInferredProjects(projectService, 1); assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); @@ -1106,7 +1080,6 @@ namespace ts.projectSystem { "files": ["${commonFile1.path}"] }`; host.reloadFS(files); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); checkNumberOfConfiguredProjects(projectService, 1); checkProjectRootFiles(project, [commonFile1.path]); @@ -1180,7 +1153,6 @@ namespace ts.projectSystem { "files": ["${file1.path}"] }`; host.reloadFS(files); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -1327,7 +1299,6 @@ namespace ts.projectSystem { host.reloadFS([file1, configFile, file2, file3, libFile]); - host.triggerDirectoryWatcherCallback(getDirectoryPath(configFile.path), configFile.path); checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 1); @@ -1611,7 +1582,6 @@ namespace ts.projectSystem { }; host.reloadFS([file1, modifiedFile2, file3]); - host.triggerFileWatcherCallback(modifiedFile2.path, FileWatcherEventKind.Changed); checkNumberOfInferredProjects(projectService, 1); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); @@ -1643,7 +1613,6 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 1 }); host.reloadFS([file1, file3]); - host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Deleted); checkNumberOfProjects(projectService, { inferredProjects: 2 }); @@ -1742,7 +1711,6 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); host.reloadFS([file1, file2, file3, configFile]); - host.triggerDirectoryWatcherCallback(getDirectoryPath(configFile.path), configFile.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]); }); @@ -1806,7 +1774,6 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, configFile]); - host.triggerDirectoryWatcherCallback(getDirectoryPath(file2.path), file2.path); host.checkTimeoutQueueLength(1); host.runQueuedTimeoutCallbacks(); // to execute throttled requests @@ -1841,7 +1808,6 @@ namespace ts.projectSystem { }; host.reloadFS([file1, file2, modifiedConfigFile]); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); @@ -1874,7 +1840,6 @@ namespace ts.projectSystem { }; host.reloadFS([file1, file2, modifiedConfigFile]); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); @@ -1954,7 +1919,6 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); host.reloadFS([file1, file2]); - host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Deleted); checkNumberOfProjects(projectService, { inferredProjects: 2 }); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); @@ -2438,7 +2402,6 @@ namespace ts.projectSystem { assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state"); host.reloadFS([f1, f2, configWithExclude]); - host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Changed); checkNumberOfProjects(projectService, { configuredProjects: 1 }); assert.isTrue(project.languageServiceEnabled, "Language service enabled"); @@ -2672,7 +2635,6 @@ namespace ts.projectSystem { // rename tsconfig.json back to lib.ts host.reloadFS([f1, f2]); - host.triggerFileWatcherCallback(tsconfig.path, FileWatcherEventKind.Deleted); projectService.openExternalProject({ projectFileName: projectName, rootFiles: toExternalFiles([f1.path, f2.path]), @@ -2817,7 +2779,6 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]); host.reloadFS([libES5, libES2015Promise, app, config2]); - host.triggerFileWatcherCallback(config1.path, FileWatcherEventKind.Changed); projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]); @@ -2950,7 +2911,6 @@ namespace ts.projectSystem { // delete t1 host.reloadFS([f1, tsconfig]); - host.triggerDirectoryWatcherCallback("/a/b/node_modules/@types", "lib1"); // run throttled operation host.runQueuedTimeoutCallbacks(); @@ -2959,7 +2919,6 @@ namespace ts.projectSystem { // create t2 host.reloadFS([f1, tsconfig, t2]); - host.triggerDirectoryWatcherCallback("/a/b/node_modules/@types", "lib2"); // run throttled operation host.runQueuedTimeoutCallbacks(); @@ -3040,16 +2999,12 @@ namespace ts.projectSystem { const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1]); - host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted); - host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; assert.equal(diags.length, 1); moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1]); - host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Deleted); - host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); // Make a change to trigger the program rebuild @@ -3092,18 +3047,12 @@ namespace ts.projectSystem { const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1, configFile]); - host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Deleted); - host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Created); - host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; assert.equal(diags.length, 1); moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1, configFile]); - host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Deleted); - host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Created); - host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path); host.runQueuedTimeoutCallbacks(); diags = session.executeCommand(getErrRequest).response; assert.equal(diags.length, 0); @@ -3176,7 +3125,6 @@ namespace ts.projectSystem { assert.equal(diags.length, 1); host.reloadFS([file1, moduleFile]); - host.triggerDirectoryWatcherCallback(getDirectoryPath(file1.path), moduleFile.path); host.runQueuedTimeoutCallbacks(); // Make a change to trigger the program rebuild @@ -3267,7 +3215,6 @@ namespace ts.projectSystem { } }`; host.reloadFS([file, configFile]); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); host.runQueuedTimeoutCallbacks(); serverEventManager.checkEventCountOfType("configFileDiag", 2); @@ -3275,7 +3222,6 @@ namespace ts.projectSystem { "compilerOptions": {} }`; host.reloadFS([file, configFile]); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); host.runQueuedTimeoutCallbacks(); serverEventManager.checkEventCountOfType("configFileDiag", 3); }); @@ -4203,7 +4149,6 @@ namespace ts.projectSystem { configFile.content = configFileContentWithoutCommentLine; host.reloadFS([file, configFile]); - host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed); const diagsAfterEdit = session.executeCommand({ type: "request", diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 8c201c84a13..ceae7582ace 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -826,7 +826,6 @@ namespace ts.projectSystem { installer.checkPendingCommands(/*expectedCount*/ 0); host.reloadFS([f, fixedPackageJson]); - host.triggerFileWatcherCallback(fixedPackageJson.path, FileWatcherEventKind.Changed); // expected install request installer.installAll(/*expectedCount*/ 1); From 2a638278911097fa2135348ce2d89c31c16562c2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jul 2017 10:43:21 -0700 Subject: [PATCH 006/144] Update the todo list --- src/server/editorServices.ts | 15 ++++----------- src/server/project.ts | 4 ---- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 7d43a1e67a6..5974dde2ba9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -674,11 +674,6 @@ namespace ts.server { return; } - // TODO: (sheetalkamat) Technically we shouldnt have to do this: - // We can report these errors in reload projects or after reload project? - const { configFileErrors } = this.convertConfigFileContentToProjectOptions(fileName); - this.reportConfigFileDiagnostics(fileName, configFileErrors, fileName); - this.logger.info(`Detected newly added tsconfig file: ${fileName}`); this.reloadProjects(); } @@ -1262,9 +1257,6 @@ namespace ts.server { private reloadConfiguredProject(project: ConfiguredProject) { // At this point, there is no reason to not have configFile in the host - // TODO: (sheetalkamat) configErrors should always be in project and there shouldnt be - // any need to return these errors - // 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. @@ -1273,8 +1265,9 @@ namespace ts.server { // reset project settings to default this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors); } - - return this.updateConfiguredProject(project, projectOptions, configFileErrors); + else { + this.updateConfiguredProject(project, projectOptions, configFileErrors); + } } /** @@ -1286,7 +1279,7 @@ namespace ts.server { project.setCompilerOptions(projectOptions.compilerOptions); if (!project.languageServiceEnabled) { // language service is already disabled - return configFileErrors; + project.setProjectErrors(configFileErrors); } project.disableLanguageService(); project.stopWatchingDirectory(); diff --git a/src/server/project.ts b/src/server/project.ts index 7e94de2afac..ebafad825a9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -649,10 +649,6 @@ namespace ts.server { for (const missingFilePath of missingFilePaths) { if (!this.missingFilesMap.has(missingFilePath)) { 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)) { fileWatcher.close(); this.missingFilesMap.delete(missingFilePath); From 96ffd53c2d3e7bebb3a0391b806f38e05157ba9d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jul 2017 10:58:31 -0700 Subject: [PATCH 007/144] Reload the configured project only once even though there are multiple files open from that project --- src/server/editorServices.ts | 38 ++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5974dde2ba9..cc1eea5c4fe 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -871,24 +871,6 @@ namespace ts.server { 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.reloadConfiguredProject(project); - } - } - } - // This is different from the method the compiler uses because // the compiler can assume it will always start searching in the // current directory (the directory in which tsc was invoked). @@ -1413,11 +1395,25 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); + const mapUpdatedProjects = createMap(); // try to reload config file for all open files for (const info of this.openFiles) { - // TODO: (sheetalkamat) batch these = multiple open files from the same project will - // end up updating project that many times - this.openOrUpdateConfiguredProjectForFile(info.fileName); + // This 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 only if we havent already done so + // otherwise we create a new one. + const configFileName = this.getConfigFileNameForFile(info.fileName); + if (configFileName) { + let project = this.findConfiguredProjectByProjectName(configFileName); + if (!project) { + project = this.openConfigFile(configFileName, info.fileName); + mapUpdatedProjects.set(configFileName, true); + } + else if (!mapUpdatedProjects.has(configFileName)) { + this.reloadConfiguredProject(project); + mapUpdatedProjects.set(configFileName, true); + } + } } this.refreshInferredProjects(); } From 6bd42b81ee49accb376fc3da560b4cec9e5cb2d6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jul 2017 11:20:53 -0700 Subject: [PATCH 008/144] When config file is deleted, apart from removing the projecty, reload the configured projects for open files from that project to ensure to pick them by another config file that can be present in parent directory --- src/server/editorServices.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index cc1eea5c4fe..328876e9c6f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -652,9 +652,11 @@ namespace ts.server { const configFileName = project.getConfigFilePath(); if (eventKind === FileWatcherEventKind.Deleted) { this.logger.info(`Config file deleted: ${configFileName}`); - // TODO: (sheetalkamat) Get the list of open files from this project - // and update the projects 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 + const openFilesInProject = this.getOrphanFiles(); + this.reloadConfiguredProjectForFiles(openFilesInProject); } else { this.logger.info(`Config file changed: ${configFileName}`); @@ -767,6 +769,17 @@ namespace ts.server { } } + private getOrphanFiles() { + let orphanFiles: ScriptInfo[]; + // for all open files + for (const f of this.openFiles) { + if (f.containingProjects.length === 0) { + (orphanFiles || (orphanFiles = [])).push(f); + } + } + return orphanFiles; + } + /** * Remove this file from the set of open, non-configured files. * @param info The file that has been closed or newly configured @@ -808,14 +821,8 @@ namespace ts.server { this.removeProject(project); } - let orphanFiles: ScriptInfo[]; - // for all open files - for (const f of this.openFiles) { - // collect orphanted files and try to re-add them as newly opened - if (f.containingProjects.length === 0) { - (orphanFiles || (orphanFiles = [])).push(f); - } - } + // collect orphanted files and try to re-add them as newly opened + const orphanFiles = this.getOrphanFiles(); // treat orphaned files as newly opened if (orphanFiles) { @@ -1395,9 +1402,14 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); + this.reloadConfiguredProjectForFiles(this.openFiles); + this.refreshInferredProjects(); + } + + reloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) { const mapUpdatedProjects = createMap(); // try to reload config file for all open files - for (const info of this.openFiles) { + for (const info of openFiles) { // This 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 only if we havent already done so @@ -1415,7 +1427,6 @@ namespace ts.server { } } } - this.refreshInferredProjects(); } /** From df6f75bc705b297ebec01ffeabaf3b2f4361d371 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jul 2017 14:43:00 -0700 Subject: [PATCH 009/144] Optimize wildcard watchers and config directory watching as now we have missing file watching as well We dont need to explicitly watch config file directory as it will be watched: - if there was no files specified, in wild card directories - if there were files specified as missing file (if the file wasnt present) --- src/server/editorServices.ts | 30 +++++----- src/server/project.ts | 104 ++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 328876e9c6f..e4e62e7dd68 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -611,7 +611,8 @@ namespace ts.server { * @param project the project that associates with this directory watcher * @param fileName the absolute file name that changed in watched directory */ - private onSourceFileInDirectoryChangedForConfiguredProject(project: ConfiguredProject, fileName: string) { + /* @internal */ + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: string) { // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. @@ -623,10 +624,13 @@ namespace ts.server { this.throttledOperations.schedule( project.getConfigFilePath(), /*delay*/250, - () => this.handleChangeInSourceFileForConfiguredProject(project, fileName)); + () => this.handleFileAddOrRemoveInWatchedDirectoryOfProject(project, fileName)); } - private handleChangeInSourceFileForConfiguredProject(project: ConfiguredProject, triggerFile: string) { + 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); @@ -676,6 +680,10 @@ namespace ts.server { return; } + // TODO: (sheetalkamat) + // 1. We should only watch tsconfig/jsconfig file here instead of watching directory + // 2. We should try reloading projects with open files in Inferred project only + // 3. We should use this watcher to answer questions to findConfigFile rather than calling host everytime this.logger.info(`Detected newly added tsconfig file: ${fileName}`); this.reloadProjects(); } @@ -1117,17 +1125,13 @@ namespace ts.server { this.documentRegistry, projectOptions.configHasFilesProperty, projectOptions.compilerOptions, - projectOptions.wildcardDirectories, /*languageServiceEnabled*/ !sizeLimitExceeded, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave); this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)); - if (!sizeLimitExceeded) { - this.watchConfigDirectoryForProject(project, projectOptions); - } - project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + project.watchWildcards(projectOptions.wildcardDirectories); project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path)); this.configuredProjects.push(project); @@ -1135,12 +1139,6 @@ namespace ts.server { return project; } - private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions): void { - if (!options.configHasFilesProperty) { - project.watchConfigDirectory((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); - } - } - private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { for (const f of files) { const rootFilename = propertyReader.getFileName(f); @@ -1271,12 +1269,12 @@ namespace ts.server { project.setProjectErrors(configFileErrors); } project.disableLanguageService(); - project.stopWatchingDirectory(); + project.stopWatchingWildCards(); project.setProjectErrors(configFileErrors); } else { project.enableLanguageService(); - this.watchConfigDirectoryForProject(project, projectOptions); + project.watchWildcards(projectOptions.wildcardDirectories); this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } } diff --git a/src/server/project.ts b/src/server/project.ts index ebafad825a9..0851b4c64b6 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -957,6 +957,8 @@ namespace ts.server { } } + type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean }; + /** * If a file is opened, the server will look for a tsconfig (or jsconfig) * and if successfull create a ConfiguredProject for it. @@ -964,9 +966,8 @@ namespace ts.server { */ export class ConfiguredProject extends Project { private typeAcquisition: TypeAcquisition; - private projectFileWatcher: FileWatcher; - private directoryWatcher: FileWatcher; - private directoriesWatchedForWildcards: Map; + private configFileWatcher: FileWatcher; + private directoriesWatchedForWildcards: Map; private typeRootsWatchers: FileWatcher[]; readonly canonicalConfigFilePath: NormalizedPath; @@ -980,7 +981,6 @@ namespace ts.server { documentRegistry: ts.DocumentRegistry, hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions, - private wildcardDirectories: Map, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean) { super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); @@ -1098,7 +1098,7 @@ namespace ts.server { } watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) { - this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); + this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); } watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { @@ -1111,49 +1111,70 @@ namespace ts.server { this.typeRootsWatchers = watchers; } - watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) { - if (this.directoryWatcher) { - return; - } - - const directoryToWatch = getDirectoryPath(this.getConfigFilePath()); - this.projectService.logger.info(`Add recursive watcher for: ${directoryToWatch}`); - this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true); - } - - watchWildcards(callback: (project: ConfiguredProject, path: string) => void) { - if (!this.wildcardDirectories) { - return; - } - const configDirectoryPath = getDirectoryPath(this.getConfigFilePath()); - - this.directoriesWatchedForWildcards = createMap(); - this.wildcardDirectories.forEach((flag, directory) => { - if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) { - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - this.projectService.logger.info(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`); - this.directoriesWatchedForWildcards.set(directory, this.projectService.host.watchDirectory( + private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) { + if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) { + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`); + this.directoriesWatchedForWildcards.set(directory, { + watcher: this.projectService.host.watchDirectory( directory, - path => callback(this, path), + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), recursive - )); - } - }); + ), + recursive + }); + } } - stopWatchingDirectory() { - if (this.directoryWatcher) { - this.directoryWatcher.close(); - this.directoryWatcher = undefined; + watchWildcards(wildcardDirectories: Map) { + if (wildcardDirectories) { + if (this.directoriesWatchedForWildcards) { + this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { + const currentFlag = wildcardDirectories.get(directory); + // Remove already watching wild card if it isnt in updated map + if (currentFlag === undefined) { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + this.directoriesWatchedForWildcards.delete(directory); + } + // Or if the recursive doesnt match (add the updated one here) + else { + const currentRecursive = (currentFlag & WatchDirectoryFlags.Recursive) !== 0; + if (currentRecursive !== recursive) { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true); + } + } + }); + } + else { + this.directoriesWatchedForWildcards = createMap(); + } + wildcardDirectories.forEach((flag, directory) => + this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false)); + } + else { + this.stopWatchingWildCards(); + } + } + + stopWatchingWildCards() { + if (this.directoriesWatchedForWildcards) { + this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + }); + this.directoriesWatchedForWildcards = undefined; } } close() { super.close(); - if (this.projectFileWatcher) { - this.projectFileWatcher.close(); - this.projectFileWatcher = undefined; + if (this.configFileWatcher) { + this.configFileWatcher.close(); + this.configFileWatcher = undefined; } if (this.typeRootsWatchers) { @@ -1163,12 +1184,7 @@ namespace ts.server { this.typeRootsWatchers = undefined; } - this.directoriesWatchedForWildcards.forEach(watcher => { - watcher.close(); - }); - this.directoriesWatchedForWildcards = undefined; - - this.stopWatchingDirectory(); + this.stopWatchingWildCards(); } addOpenRef() { From 9ff9476b4aed078d44cb5446dd2fd59c1ab1341b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 10 Jul 2017 14:32:36 -0700 Subject: [PATCH 010/144] Handle the deleted file in update graph better, so that in next update it is possible to schedule the update graph of project --- src/compiler/types.ts | 4 ++++ src/server/editorServices.ts | 4 ---- src/server/lsHost.ts | 40 +++++++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 14b80325aa3..b1610c76088 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3975,6 +3975,8 @@ namespace ts { resolvedModule: ResolvedModuleFull | undefined; /* @internal */ failedLookupLocations: string[]; + /*@internal*/ + isInvalidated?: boolean; } export interface ResolvedTypeReferenceDirective { @@ -3987,6 +3989,8 @@ namespace ts { export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; failedLookupLocations: string[]; + /*@internal*/ + isInvalidated?: boolean; } export interface CompilerHost extends ModuleResolutionHost { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e4e62e7dd68..e5fb2006379 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -361,8 +361,6 @@ namespace ts.server { readonly toCanonicalFileName: (f: string) => string; - public lastDeletedFile: ScriptInfo; - public readonly host: ServerHost; public readonly logger: Logger; public readonly cancellationToken: HostCancellationToken; @@ -571,7 +569,6 @@ namespace ts.server { if (!info.isScriptOpen()) { this.filenameToScriptInfo.delete(info.path); - this.lastDeletedFile = info; // capture list of projects since detachAllProjects will wipe out original list const containingProjects = info.containingProjects.slice(); @@ -580,7 +577,6 @@ namespace ts.server { // update projects to make sure that set of referenced files is correct this.updateProjectGraphs(containingProjects); - this.lastDeletedFile = undefined; if (!this.eventHandler) { return; diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 55513f1a285..c8ab7d3c0f3 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -3,6 +3,7 @@ /// namespace ts.server { + type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost { private compilationSettings: ts.CompilerOptions; private readonly resolvedModuleNames = createMap>(); @@ -62,7 +63,7 @@ namespace ts.server { return collected; } - private resolveNamesWithLocalCache( + private resolveNamesWithLocalCache( names: string[], containingFile: string, cache: Map>, @@ -77,7 +78,6 @@ namespace ts.server { const newResolutions: Map = createMap(); const resolvedModules: R[] = []; const compilerOptions = this.getCompilationSettings(); - const lastDeletedFileName = this.project.projectService.lastDeletedFile && this.project.projectService.lastDeletedFile.fileName; for (const name of names) { // check if this is a duplicate entry in the list @@ -112,7 +112,7 @@ namespace ts.server { if (oldResolution === newResolution) { return true; } - if (!oldResolution || !newResolution) { + if (!oldResolution || !newResolution || oldResolution.isInvalidated) { return false; } const oldResult = getResult(oldResolution); @@ -127,13 +127,13 @@ namespace ts.server { } function moduleResolutionIsValid(resolution: T): boolean { - if (!resolution) { + if (!resolution || resolution.isInvalidated) { return false; } const result = getResult(resolution); if (result) { - return getResultFileName(result) !== lastDeletedFileName; + return true; } // consider situation if we have no candidate locations as valid resolution. @@ -234,8 +234,34 @@ namespace ts.server { } notifyFileRemoved(info: ScriptInfo) { - this.resolvedModuleNames.delete(info.path); - this.resolvedTypeReferenceDirectives.delete(info.path); + this.invalidateResolutionOfDeletedFile(info, this.resolvedModuleNames, + m => m.resolvedModule, r => r.resolvedFileName); + this.invalidateResolutionOfDeletedFile(info, this.resolvedTypeReferenceDirectives, + m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); + } + + private invalidateResolutionOfDeletedFile( + deletedInfo: ScriptInfo, + cache: Map>, + getResult: (s: T) => R, + getResultFileName: (result: R) => string | undefined) { + cache.forEach((value, path) => { + if (path === deletedInfo.path) { + cache.delete(path); + } + else if (value) { + value.forEach((resolution) => { + if (resolution && !resolution.isInvalidated) { + const result = getResult(resolution); + if (result) { + if (getResultFileName(result) === deletedInfo.path) { + resolution.isInvalidated = true; + } + } + } + }); + } + }); } setCompilationSettings(opt: ts.CompilerOptions) { From 62871cc0f9a87344bc6b6e6f90a7b3885209a0d5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 11 Jul 2017 13:38:12 -0700 Subject: [PATCH 011/144] Only update file list when there are changes in the watched directories --- src/compiler/commandLineParser.ts | 90 ++++++++++++------- src/compiler/types.ts | 11 +++ .../unittests/tsserverProjectSystem.ts | 3 +- src/server/editorServices.ts | 71 +++++++-------- src/server/project.ts | 3 + 5 files changed, 107 insertions(+), 71 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 422789f420f..94b6540f16a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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 = raw["files"]; - if (fileNames.length === 0) { + filesSpecs = 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(); - 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 }; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b1610c76088..88bfff872d5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3731,6 +3731,7 @@ namespace ts { errors: Diagnostic[]; wildcardDirectories?: MapLike; 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; + } + export interface ExpandResult { fileNames: string[]; wildcardDirectories: MapLike; + spec: ConfigFileSpecs; } /* @internal */ diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 51eea84f49b..bd5bfb1841d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -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]); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e5fb2006379..a19ea125ebf 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -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(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader) { @@ -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(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { + private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap: Map = createMap(); @@ -1219,7 +1212,10 @@ namespace ts.server { } }); } + } + private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, 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); diff --git a/src/server/project.ts b/src/server/project.ts index 0851b4c64b6..acc826c7730 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -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 */ From 48c651317eeeb3c26a05f5d86dc48dd924c9f833 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 11 Jul 2017 14:18:56 -0700 Subject: [PATCH 012/144] Update types instantly when the type root changes. --- src/server/editorServices.ts | 2 +- src/server/project.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a19ea125ebf..5060238521f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -595,8 +595,8 @@ namespace ts.server { private onTypeRootFileChanged(project: ConfiguredProject, fileName: string) { this.logger.info(`Type root file ${fileName} changed`); + project.updateTypes(); this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { - project.updateTypes(); this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) this.refreshInferredProjects(); }); diff --git a/src/server/project.ts b/src/server/project.ts index acc826c7730..91687faa0b9 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -294,7 +294,6 @@ namespace ts.server { updateTypes() { this.typesVersion++; this.markAsDirty(); - this.updateGraph(); } close() { From 19a6a003f59075049870d33f4201478a0fd15055 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 11 Jul 2017 16:10:44 -0700 Subject: [PATCH 013/144] Cache the read directory results so that it doesnt end up reading it all the time --- .../unittests/tsserverProjectSystem.ts | 6 +-- src/server/editorServices.ts | 22 ++++---- src/server/lsHost.ts | 52 +++++++++++++++++++ src/server/project.ts | 10 ++-- 4 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index bd5bfb1841d..1c368cad69c 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -574,7 +574,7 @@ namespace ts.projectSystem { const path = this.toFullPath(s); const folder = this.fs.get(path); if (isFolder(folder)) { - return map(folder.entries, x => getBaseFileName(x.fullPath)); + return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); } Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); return []; @@ -590,10 +590,10 @@ namespace ts.projectSystem { if (isFolder(dirEntry)) { dirEntry.entries.forEach((entry) => { if (isFolder(entry)) { - result.directories.push(entry.fullPath); + result.directories.push(getBaseFileName(entry.fullPath)); } else if (isFile(entry)) { - result.files.push(entry.fullPath); + result.files.push(getBaseFileName(entry.fullPath)); } else { Debug.fail("Unknown entry"); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5060238521f..30da6631a49 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -608,7 +608,9 @@ namespace ts.server { * @param fileName the absolute file name that changed in watched directory */ /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: string) { + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: Path) { + project.cachedParseConfigHost.clearCacheForFile(fileName); + // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. @@ -621,7 +623,7 @@ namespace ts.server { 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 result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.cachedParseConfigHost, this.hostConfiguration.extraFileExtensions); const errors = project.getAllProjectErrors(); if (result.fileNames.length === 0) { if (!configFileSpecs.filesSpecs) { @@ -945,7 +947,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedParseConfigHost: CachedParseConfigHost) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -957,7 +959,7 @@ namespace ts.server { const errors = result.parseDiagnostics; const parsedCommandLine = parseJsonSourceFileConfigFileContent( result, - this.host, + cachedParseConfigHost, getDirectoryPath(configFilename), /*existingOptions*/ {}, configFilename, @@ -1105,7 +1107,7 @@ namespace ts.server { }); } - private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, clientFileName?: string) { + private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedParseConfigHost: CachedParseConfigHost, clientFileName?: string) { const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( configFileName, @@ -1115,7 +1117,7 @@ namespace ts.server { projectOptions.compilerOptions, /*languageServiceEnabled*/ !sizeLimitExceeded, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave); - + project.cachedParseConfigHost = cachedParseConfigHost; this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); project.configFileSpecs = configFileSpecs; @@ -1149,12 +1151,13 @@ namespace ts.server { } private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName); + const cachedParseConfigHost = new CachedParseConfigHost(this.host); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedParseConfigHost); if (success) { this.logger.info(`Opened configuration file ${configFileName}`); } - return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, clientFileName); + return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedParseConfigHost, clientFileName); } private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader) { @@ -1239,7 +1242,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, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath()); + project.cachedParseConfigHost.clearCache(); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), project.cachedParseConfigHost); project.configFileSpecs = configFileSpecs; if (!success) { // reset project settings to default diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index c8ab7d3c0f3..720748eb5d0 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -4,6 +4,58 @@ namespace ts.server { type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; + + export class CachedParseConfigHost implements ParseConfigHost { + useCaseSensitiveFileNames: boolean; + private getCanonicalFileName: (fileName: string) => string; + private cachedReadDirectoryResult = createMap(); + constructor(private readonly host: ServerHost) { + this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; + this.getCanonicalFileName = createGetCanonicalFileName(this.useCaseSensitiveFileNames); + } + + private getFileSystemEntries(rootDir: string) { + const path = ts.toPath(rootDir, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const cachedResult = this.cachedReadDirectoryResult.get(path); + if (cachedResult) { + return cachedResult; + } + + const resultFromHost: FileSystemEntries = { + files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], + directories: this.host.getDirectories(rootDir) + }; + + this.cachedReadDirectoryResult.set(path, resultFromHost); + return resultFromHost; + } + + readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { + return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.host.getCurrentDirectory(), depth, path => this.getFileSystemEntries(path)); + } + + fileExists(fileName: string): boolean { + const path = ts.toPath(fileName, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const result = this.getFileSystemEntries(getDirectoryPath(path)); + return contains(result.files, fileName); + } + + readFile(path: string): string { + return this.host.readFile(path); + } + + clearCacheForFile(fileName: Path) { + this.cachedReadDirectoryResult.delete(fileName); + this.cachedReadDirectoryResult.delete(getDirectoryPath(fileName)); + } + + clearCache() { + this.cachedReadDirectoryResult = createMap(); + } + + // TODO: (sheetalkamat) to cache getFileSize as well as fileExists and readFile + } + export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost { private compilationSettings: ts.CompilerOptions; private readonly resolvedModuleNames = createMap>(); diff --git a/src/server/project.ts b/src/server/project.ts index 91687faa0b9..cfd234b64ea 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -972,6 +972,7 @@ namespace ts.server { /*@internal*/ configFileSpecs: ConfigFileSpecs; + cachedParseConfigHost: CachedParseConfigHost; private plugins: PluginModule[] = []; @@ -1113,14 +1114,14 @@ namespace ts.server { this.typeRootsWatchers = watchers; } - private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) { + private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, getCanonicalFileName: (fileName: string) => string, replaceExisting: boolean) { if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`); this.directoriesWatchedForWildcards.set(directory, { watcher: this.projectService.host.watchDirectory( directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toPath(path, directory, getCanonicalFileName)), recursive ), recursive @@ -1129,6 +1130,7 @@ namespace ts.server { } watchWildcards(wildcardDirectories: Map) { + const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames); if (wildcardDirectories) { if (this.directoriesWatchedForWildcards) { this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { @@ -1145,7 +1147,7 @@ namespace ts.server { if (currentRecursive !== recursive) { this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); watcher.close(); - this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true); + this.addWatcherForDirectory(currentFlag, directory, getCanonicalFileName, /*replaceExisting*/ true); } } }); @@ -1154,7 +1156,7 @@ namespace ts.server { this.directoriesWatchedForWildcards = createMap(); } wildcardDirectories.forEach((flag, directory) => - this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false)); + this.addWatcherForDirectory(flag, directory, getCanonicalFileName, /*replaceExisting*/ false)); } else { this.stopWatchingWildCards(); From 68def1b1f31eb1b45a83023b32f6d292839a4152 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 11 Jul 2017 16:10:44 -0700 Subject: [PATCH 014/144] Use the cached file exists/directory exists for configured project since we watch the needed files --- .../unittests/tsserverProjectSystem.ts | 41 ++--- src/server/editorServices.ts | 34 +++-- src/server/lsHost.ts | 144 ++++++++++++++++-- src/server/project.ts | 47 +++--- src/server/scriptInfo.ts | 3 + src/server/utilities.ts | 4 +- 6 files changed, 204 insertions(+), 69 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 1c368cad69c..4426e52ab66 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -261,10 +261,11 @@ namespace ts.projectSystem { return s && typeof (s).content === "string"; } - function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], fileName: string) { + function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) { if (callbacks) { const cbs = callbacks.slice(); for (const cb of cbs) { + const fileName = getRelativeFilePath(); cb(fileName); } } @@ -407,7 +408,7 @@ namespace ts.projectSystem { // Update file if (currentEntry.content !== fileOrFolder.content) { currentEntry.content = fileOrFolder.content; - this.invokeFileWatcher(currentEntry.path, FileWatcherEventKind.Changed); + this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); } } else { @@ -481,9 +482,9 @@ namespace ts.projectSystem { this.fs.set(fileOrFolder.path, fileOrFolder); if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.path, FileWatcherEventKind.Created); + this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created); } - this.invokeDirectoryWatcher(folder.path, fileOrFolder.path); + this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath); } private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { @@ -496,12 +497,12 @@ namespace ts.projectSystem { this.fs.delete(fileOrFolder.path); if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.path, FileWatcherEventKind.Deleted); + this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted); } else { Debug.assert(fileOrFolder.entries.length === 0); - invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), fileOrFolder.path); - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), fileOrFolder.path); + invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); } if (basePath !== fileOrFolder.path) { @@ -509,25 +510,29 @@ namespace ts.projectSystem { this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); } else { - this.invokeRecursiveDirectoryWatcher(baseFolder.path, fileOrFolder.path); + this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath); } } } - private invokeFileWatcher(filePath: Path, eventId: FileWatcherEventKind) { - const callbacks = this.watchedFiles.get(filePath); - invokeFileWatcher(callbacks, filePath, eventId); + private invokeFileWatcher(fileFullPath: string, eventId: FileWatcherEventKind) { + const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); + invokeFileWatcher(callbacks, getBaseFileName(fileFullPath), eventId); } - private invokeDirectoryWatcher(folderPath: Path, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectories.get(folderPath), fileName); - this.invokeRecursiveDirectoryWatcher(folderPath, fileName); + private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { + return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); } - private invokeRecursiveDirectoryWatcher(path: Path, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(path), fileName); - const basePath = getDirectoryPath(path); - if (path !== basePath) { + private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectories.get(this.toPath(folderFullPath)), () => this.getRelativePathToDirectory(folderFullPath, fileName)); + this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); + } + + private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), () => this.getRelativePathToDirectory(fullPath, fileName)); + const basePath = getDirectoryPath(fullPath); + if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { this.invokeRecursiveDirectoryWatcher(basePath, fileName); } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 30da6631a49..ed2f50acd39 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -593,8 +593,9 @@ namespace ts.server { this.printProjects(); } - private onTypeRootFileChanged(project: ConfiguredProject, fileName: string) { + private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { this.logger.info(`Type root file ${fileName} changed`); + project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); project.updateTypes(); this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) @@ -608,8 +609,8 @@ namespace ts.server { * @param fileName the absolute file name that changed in watched directory */ /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: Path) { - project.cachedParseConfigHost.clearCacheForFile(fileName); + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: NormalizedPath) { + project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". @@ -623,7 +624,7 @@ namespace ts.server { 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(), project.cachedParseConfigHost, this.hostConfiguration.extraFileExtensions); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); const errors = project.getAllProjectErrors(); if (result.fileNames.length === 0) { if (!configFileSpecs.filesSpecs) { @@ -947,7 +948,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedParseConfigHost: CachedParseConfigHost) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -959,7 +960,7 @@ namespace ts.server { const errors = result.parseDiagnostics; const parsedCommandLine = parseJsonSourceFileConfigFileContent( result, - cachedParseConfigHost, + cachedServerHost, getDirectoryPath(configFilename), /*existingOptions*/ {}, configFilename, @@ -1107,7 +1108,7 @@ namespace ts.server { }); } - private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedParseConfigHost: CachedParseConfigHost, clientFileName?: string) { + private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) { const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( configFileName, @@ -1116,8 +1117,8 @@ namespace ts.server { projectOptions.configHasFilesProperty, projectOptions.compilerOptions, /*languageServiceEnabled*/ !sizeLimitExceeded, - projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave); - project.cachedParseConfigHost = cachedParseConfigHost; + projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, + cachedServerHost); this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); project.configFileSpecs = configFileSpecs; @@ -1136,7 +1137,7 @@ namespace ts.server { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); const fileName = toNormalizedPath(rootFilename); - if (this.host.fileExists(rootFilename)) { + if (project.lsHost.host.fileExists(rootFilename)) { const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); project.addRoot(info); } @@ -1151,13 +1152,13 @@ namespace ts.server { } private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { - const cachedParseConfigHost = new CachedParseConfigHost(this.host); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedParseConfigHost); + const cachedServerHost = new CachedServerHost(this.host); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); if (success) { this.logger.info(`Opened configuration file ${configFileName}`); } - return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedParseConfigHost, clientFileName); + return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName); } private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader) { @@ -1169,7 +1170,7 @@ namespace ts.server { const normalizedPath = toNormalizedPath(newRootFile); let scriptInfo: ScriptInfo | NormalizedPath; let path: Path; - if (!this.host.fileExists(newRootFile)) { + if (!project.lsHost.fileExists(newRootFile)) { path = normalizedPathToPath(normalizedPath, this.host.getCurrentDirectory(), this.toCanonicalFileName); const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { @@ -1242,8 +1243,9 @@ 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. - project.cachedParseConfigHost.clearCache(); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), project.cachedParseConfigHost); + const host = project.getCachedServerHost(); + host.clearCache(); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), host); project.configFileSpecs = configFileSpecs; if (!success) { // reset project settings to default diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 720748eb5d0..b050a7d26d9 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -5,17 +5,34 @@ namespace ts.server { type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; - export class CachedParseConfigHost implements ParseConfigHost { + export class CachedServerHost implements ServerHost { + args: string[]; + newLine: string; useCaseSensitiveFileNames: boolean; + + readonly trace: (s: string) => void; + readonly realpath?: (path: string) => string; + private getCanonicalFileName: (fileName: string) => string; private cachedReadDirectoryResult = createMap(); + private readonly currentDirectory: string; + constructor(private readonly host: ServerHost) { + this.args = host.args; + this.newLine = host.newLine; this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; this.getCanonicalFileName = createGetCanonicalFileName(this.useCaseSensitiveFileNames); + if (host.trace) { + this.trace = s => host.trace(s); + } + if (this.host.realpath) { + this.realpath = path => this.host.realpath(path); + } + this.currentDirectory = this.host.getCurrentDirectory(); } private getFileSystemEntries(rootDir: string) { - const path = ts.toPath(rootDir, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const path = ts.toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); const cachedResult = this.cachedReadDirectoryResult.get(path); if (cachedResult) { return cachedResult; @@ -23,52 +40,148 @@ namespace ts.server { const resultFromHost: FileSystemEntries = { files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], - directories: this.host.getDirectories(rootDir) + directories: this.host.getDirectories(rootDir) || [] }; this.cachedReadDirectoryResult.set(path, resultFromHost); return resultFromHost; } + write(s: string) { + return this.host.write(s); + } + + writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { + const path = ts.toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); + const baseFileName = getBaseFileName(toNormalizedPath(fileName)); + if (result && !some(result.files, file => this.fileNameEqual(file, baseFileName))) { + result.files.push(baseFileName); + } + return this.host.writeFile(fileName, data, writeByteOrderMark); + } + + resolvePath(path: string) { + return this.host.resolvePath(path); + } + + createDirectory(path: string) { + Debug.fail(`Why is createDirectory called on the cached server for ${path}`); + } + + getExecutingFilePath() { + return this.host.getExecutingFilePath(); + } + + getCurrentDirectory() { + return this.currentDirectory; + } + + exit(exitCode?: number) { + Debug.fail(`Why is exit called on the cached server: ${exitCode}`); + } + + getEnvironmentVariable(name: string) { + Debug.fail(`Why is getEnvironmentVariable called on the cached server: ${name}`); + return this.host.getEnvironmentVariable(name); + } + + getDirectories(path: string) { + return this.getFileSystemEntries(path).directories; + } + readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.host.getCurrentDirectory(), depth, path => this.getFileSystemEntries(path)); + return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path)); } fileExists(fileName: string): boolean { - const path = ts.toPath(fileName, this.host.getCurrentDirectory(), this.getCanonicalFileName); - const result = this.getFileSystemEntries(getDirectoryPath(path)); - return contains(result.files, fileName); + const path = ts.toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); + const baseName = getBaseFileName(toNormalizedPath(fileName)); + return (result && some(result.files, file => this.fileNameEqual(file, baseName))) || this.host.fileExists(fileName); } - readFile(path: string): string { - return this.host.readFile(path); + directoryExists(dirPath: string) { + const path = ts.toPath(dirPath, this.currentDirectory, this.getCanonicalFileName); + return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath); } - clearCacheForFile(fileName: Path) { - this.cachedReadDirectoryResult.delete(fileName); - this.cachedReadDirectoryResult.delete(getDirectoryPath(fileName)); + readFile(path: string, encoding?: string): string { + return this.host.readFile(path, encoding); + } + + private fileNameEqual(name1: string, name2: string) { + return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2); + } + + private updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { + if (some(entries, entry => this.fileNameEqual(entry, baseName))) { + if (!isValid) { + filterMutate(entries, entry => !this.fileNameEqual(entry, baseName)); + } + } + else if (isValid) { + entries.push(baseName); + } + } + + addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) { + const path = toPath(fileOrFolder, this.currentDirectory, this.getCanonicalFileName); + const existingResult = this.cachedReadDirectoryResult.get(path); + if (existingResult) { + if (!this.host.directoryExists(fileOrFolder)) { + this.cachedReadDirectoryResult.delete(path); + } + } + else { + // Was this earlier file + const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); + if (parentResult) { + const baseName = getBaseFileName(fileOrFolder); + if (parentResult) { + this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); + this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); + } + } + } } clearCache() { this.cachedReadDirectoryResult = createMap(); } - // TODO: (sheetalkamat) to cache getFileSize as well as fileExists and readFile + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { + return this.host.setTimeout(callback, ms, ...args); + } + clearTimeout(timeoutId: any) { + return this.host.clearTimeout(timeoutId); + } + setImmediate(callback: (...args: any[]) => void, ...args: any[]) { + this.host.setImmediate(callback, ...args); + } + clearImmediate(timeoutId: any) { + this.host.clearImmediate(timeoutId); + } + } export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost { private compilationSettings: ts.CompilerOptions; private readonly resolvedModuleNames = createMap>(); private readonly resolvedTypeReferenceDirectives = createMap>(); - private readonly getCanonicalFileName: (fileName: string) => string; + /* @internal */ + readonly getCanonicalFileName: (fileName: string) => string; private filesWithChangedSetOfUnresolvedImports: Path[]; private resolveModuleName: typeof resolveModuleName; readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; + /*@internal*/ + host: ServerHost; - constructor(private readonly host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { + constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { + this.host = host; this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); @@ -103,6 +216,7 @@ namespace ts.server { dispose() { this.project = undefined; this.resolveModuleName = undefined; + this.host = undefined; } public startRecordingFilesWithChangedResolutions() { diff --git a/src/server/project.ts b/src/server/project.ts index cfd234b64ea..b6f040b5a26 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -127,7 +127,8 @@ namespace ts.server { public languageServiceEnabled = true; - protected lsHost: LSHost; + /*@internal*/ + lsHost: LSHost; builder: Builder; /** @@ -194,7 +195,8 @@ namespace ts.server { hasExplicitListOfFiles: boolean, languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, - public compileOnSaveEnabled: boolean) { + public compileOnSaveEnabled: boolean, + host: ServerHost) { if (!this.compilerOptions) { this.compilerOptions = ts.getDefaultCompilerOptions(); @@ -208,7 +210,7 @@ namespace ts.server { this.setInternalCompilerOptionsForEmittingJsFiles(); - this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken); + this.lsHost = new LSHost(host, this, this.projectService.cancellationToken); this.lsHost.setCompilationSettings(this.compilerOptions); this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry); @@ -647,11 +649,16 @@ namespace ts.server { // Missing files that are not yet watched should be added to the map. for (const missingFilePath of missingFilePaths) { 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) => { if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { fileWatcher.close(); this.missingFilesMap.delete(missingFilePath); + if (this.projectKind === ProjectKind.Configured) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + } + // When a missing file is created, we should update the graph. this.markAsDirty(); this.updateGraph(); @@ -849,7 +856,7 @@ namespace ts.server { } const allFileNames = arrayFrom(referencedFiles.keys()) as Path[]; - return filter(allFileNames, file => this.projectService.host.fileExists(file)); + return filter(allFileNames, file => this.lsHost.host.fileExists(file)); } // remove a root file from project @@ -911,7 +918,8 @@ namespace ts.server { /*files*/ undefined, /*languageServiceEnabled*/ true, compilerOptions, - /*compileOnSaveEnabled*/ false); + /*compileOnSaveEnabled*/ false, + projectService.host); } addRoot(info: ScriptInfo) { @@ -972,7 +980,6 @@ namespace ts.server { /*@internal*/ configFileSpecs: ConfigFileSpecs; - cachedParseConfigHost: CachedParseConfigHost; private plugins: PluginModule[] = []; @@ -985,12 +992,17 @@ namespace ts.server { hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions, languageServiceEnabled: boolean, - public compileOnSaveEnabled: boolean) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); + public compileOnSaveEnabled: boolean, + cachedServerHost: CachedServerHost) { + super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedServerHost); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); } + getCachedServerHost() { + return this.lsHost.host as CachedServerHost; + } + getConfigFilePath() { return this.getProjectName(); } @@ -1104,24 +1116,24 @@ namespace ts.server { this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); } - watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { + watchTypeRoots(callback: (project: ConfiguredProject, path: NormalizedPath) => void) { const roots = this.getEffectiveTypeRoots(); const watchers: FileWatcher[] = []; for (const root of roots) { this.projectService.logger.info(`Add type root watcher for: ${root}`); - watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, path), /*recursive*/ false)); + watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, toNormalizedPath(getNormalizedAbsolutePath(path, root))), /*recursive*/ false)); } this.typeRootsWatchers = watchers; } - private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, getCanonicalFileName: (fileName: string) => string, replaceExisting: boolean) { + private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) { if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`); this.directoriesWatchedForWildcards.set(directory, { watcher: this.projectService.host.watchDirectory( directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toPath(path, directory, getCanonicalFileName)), + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toNormalizedPath(getNormalizedAbsolutePath(path, directory))), recursive ), recursive @@ -1130,7 +1142,6 @@ namespace ts.server { } watchWildcards(wildcardDirectories: Map) { - const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames); if (wildcardDirectories) { if (this.directoriesWatchedForWildcards) { this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { @@ -1147,7 +1158,7 @@ namespace ts.server { if (currentRecursive !== recursive) { this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); watcher.close(); - this.addWatcherForDirectory(currentFlag, directory, getCanonicalFileName, /*replaceExisting*/ true); + this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true); } } }); @@ -1156,7 +1167,7 @@ namespace ts.server { this.directoriesWatchedForWildcards = createMap(); } wildcardDirectories.forEach((flag, directory) => - this.addWatcherForDirectory(flag, directory, getCanonicalFileName, /*replaceExisting*/ false)); + this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false)); } else { this.stopWatchingWildCards(); @@ -1201,7 +1212,7 @@ namespace ts.server { } getEffectiveTypeRoots() { - return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || []; + return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; } } @@ -1218,7 +1229,7 @@ namespace ts.server { languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, private readonly projectFilePath?: string) { - super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); + super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host); } getProjectRootPath() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index ee5e45c7c47..90853124815 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -254,6 +254,9 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { + if (p.projectKind === ProjectKind.Configured) { + (p.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(this.fileName); + } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways p.removeFile(this, /*detachFromProjects*/ false); diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 10290fe864d..c2126d4ce45 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -42,7 +42,7 @@ namespace ts.server { return ""; case ProjectKind.External: const projectName = normalizeSlashes(project.getProjectName()); - return project.projectService.host.fileExists(projectName) ? getDirectoryPath(projectName) : projectName; + return project.lsHost.host.fileExists(projectName) ? getDirectoryPath(projectName) : projectName; } } @@ -273,4 +273,4 @@ namespace ts.server { } } } -} \ No newline at end of file +} From 029b1f25e442f0982cb103f47a03881bb0176019 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 12 Jul 2017 12:50:52 -0700 Subject: [PATCH 015/144] Fixes the fourslash runner tests by handling hosts that cannot support read directory or getDirectories --- src/server/lsHost.ts | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index b050a7d26d9..133db8cac2c 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -47,6 +47,20 @@ namespace ts.server { return resultFromHost; } + private canWorkWithCacheForDir(rootDir: string) { + // Some of the hosts might not be able to handle read directory or getDirectories + const path = ts.toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); + if (this.cachedReadDirectoryResult.get(path)) { + return true; + } + try { + return this.getFileSystemEntries(rootDir); + } + catch (_e) { + return false; + } + } + write(s: string) { return this.host.write(s); } @@ -86,12 +100,18 @@ namespace ts.server { return this.host.getEnvironmentVariable(name); } - getDirectories(path: string) { - return this.getFileSystemEntries(path).directories; + getDirectories(rootDir: string) { + if (this.canWorkWithCacheForDir(rootDir)) { + return this.getFileSystemEntries(rootDir).directories; + } + return this.host.getDirectories(rootDir); } readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path)); + if (this.canWorkWithCacheForDir(rootDir)) { + return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path)); + } + return this.host.readDirectory(rootDir, extensions, excludes, includes, depth); } fileExists(fileName: string): boolean { From f338a7025721f78d9dba9c1f9d3f506976a811dd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 12 Jul 2017 13:39:11 -0700 Subject: [PATCH 016/144] Remove the done TODO --- src/server/editorServices.ts | 3 +-- src/server/utilities.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index ed2f50acd39..5b2e4284857 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -623,7 +623,6 @@ namespace ts.server { 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(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); const errors = project.getAllProjectErrors(); if (result.fileNames.length === 0) { @@ -1137,7 +1136,7 @@ namespace ts.server { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); const fileName = toNormalizedPath(rootFilename); - if (project.lsHost.host.fileExists(rootFilename)) { + if (project.lsHost.fileExists(rootFilename)) { const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); project.addRoot(info); } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index c2126d4ce45..416b148fc0d 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -42,7 +42,7 @@ namespace ts.server { return ""; case ProjectKind.External: const projectName = normalizeSlashes(project.getProjectName()); - return project.lsHost.host.fileExists(projectName) ? getDirectoryPath(projectName) : projectName; + return project.projectService.host.fileExists(projectName) ? getDirectoryPath(projectName) : projectName; } } From 8fedcf78c780004f2fd1f4c0eb4ce81263c97c1c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 12 Jul 2017 19:34:01 -0700 Subject: [PATCH 017/144] TODOs for the scheduling update graph and referesh projects --- src/server/editorServices.ts | 57 +++++++++++++++++++++++------------- src/server/project.ts | 2 ++ src/server/session.ts | 1 + 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5b2e4284857..9aa627e75a1 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -443,6 +443,8 @@ namespace ts.server { this.typingsCache.deleteTypingsForProject(response.projectName); break; } + project.markAsDirty(); + // TODO: (sheetalkamat) Schedule Update graph instead of immediate project.updateGraph(); } @@ -456,6 +458,7 @@ namespace ts.server { proj.setCompilerOptions(this.compilerOptionsForInferredProjects); proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave; } + // TODO: (sheetalkamat) (ensure updated projects) this.updateProjectGraphs(this.inferredProjects); } @@ -476,6 +479,7 @@ namespace ts.server { getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean) { if (refreshInferredProjects) { + // TODO: (sheetalkamat) needs to update inferred projects too ? this.ensureInferredProjectsUpToDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); @@ -498,6 +502,7 @@ namespace ts.server { this.updateProjectGraphs(projectsToUpdate); this.changedFiles = undefined; } + // TODO: (sheetalkamat) should also refresh inferred projects here since we are postponing the changes in closed files } private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { @@ -554,7 +559,7 @@ namespace ts.server { // file has been changed which might affect the set of referenced files in projects that include // this file and set of inferred projects info.reloadFromFile(); - this.updateProjectGraphs(info.containingProjects); + this.recordChangedFile(info); } } } @@ -575,6 +580,7 @@ namespace ts.server { info.detachAllProjects(); + // TODO: (sheetalkamat) Schedule this too ? // update projects to make sure that set of referenced files is correct this.updateProjectGraphs(containingProjects); @@ -597,10 +603,13 @@ namespace ts.server { this.logger.info(`Type root file ${fileName} changed`); project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); project.updateTypes(); - this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { - this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) + // TODO: (sheetalkamat) schedule project update and referesh inferred projects instead + + //this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { + // this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) + project.updateGraph(); this.refreshInferredProjects(); - }); + //}); } /** @@ -655,10 +664,12 @@ namespace ts.server { // 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 const openFilesInProject = this.getOrphanFiles(); + // TODO: (sheetalkamat) can this be scheduled too this.reloadConfiguredProjectForFiles(openFilesInProject); } else { this.logger.info(`Config file changed: ${configFileName}`); + // TODO: (sheetalkamat) This should be scheduled and marked for reload of configured project since we dont want to updating this on every change this.reloadConfiguredProject(project); this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); } @@ -923,7 +934,7 @@ namespace ts.server { function printProjects(logger: Logger, projects: Project[], counter: number) { for (const project of projects) { - project.updateGraph(); + // Print shouldnt update the graph. It should emit whatever state the project is currently in logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); logger.info(project.filesToString()); logger.info("-----------------------------------------------"); @@ -1215,6 +1226,7 @@ namespace ts.server { } }); } + project.markAsDirty(); // Just to ensure that even if root files dont change, the changes to the non root file are picked up } private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { @@ -1229,6 +1241,7 @@ namespace ts.server { } project.setProjectErrors(configFileErrors); + // This doesnt need scheduling since its either creation or reload of the project project.updateGraph(); } @@ -1261,20 +1274,14 @@ namespace ts.server { */ private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) { if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { - project.setCompilerOptions(projectOptions.compilerOptions); - if (!project.languageServiceEnabled) { - // language service is already disabled - project.setProjectErrors(configFileErrors); - } project.disableLanguageService(); project.stopWatchingWildCards(); - project.setProjectErrors(configFileErrors); } else { project.enableLanguageService(); project.watchWildcards(projectOptions.wildcardDirectories); - this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } + this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) { @@ -1540,9 +1547,19 @@ namespace ts.server { return files; } + private recordChangedFile(scriptInfo: ScriptInfo) { + if (!this.changedFiles) { + this.changedFiles = [scriptInfo]; + } + else if (this.changedFiles.indexOf(scriptInfo) < 0) { + this.changedFiles.push(scriptInfo); + } + // TODO: (sheetalkamat) Schedule the project update graphs + this.updateProjectGraphs(scriptInfo.containingProjects); + } + /* @internal */ applyChangesInOpenFiles(openFiles: protocol.ExternalFile[], changedFiles: protocol.ChangedOpenFile[], closedFiles: string[]): void { - const recordChangedFiles = changedFiles && !openFiles && !closedFiles; if (openFiles) { for (const file of openFiles) { const scriptInfo = this.getScriptInfo(file.fileName); @@ -1561,14 +1578,7 @@ namespace ts.server { const change = file.changes[i]; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } - if (recordChangedFiles) { - if (!this.changedFiles) { - this.changedFiles = [scriptInfo]; - } - else if (this.changedFiles.indexOf(scriptInfo) < 0) { - this.changedFiles.push(scriptInfo); - } - } + this.recordChangedFile(scriptInfo); } } @@ -1580,6 +1590,7 @@ namespace ts.server { // if files were open or closed then explicitly refresh list of inferred projects // otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update if (openFiles || closedFiles) { + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.refreshInferredProjects(); } } @@ -1603,6 +1614,7 @@ namespace ts.server { } this.externalProjectToConfiguredProjectMap.delete(fileName); if (shouldRefreshInferredProjects && !suppressRefresh) { + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.refreshInferredProjects(); } } @@ -1612,6 +1624,7 @@ namespace ts.server { if (externalProject) { this.removeProject(externalProject); if (!suppressRefresh) { + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.refreshInferredProjects(); } } @@ -1636,6 +1649,7 @@ namespace ts.server { this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); }); + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.refreshInferredProjects(); } @@ -1830,6 +1844,7 @@ namespace ts.server { this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); } if (!suppressRefreshOfInferredProjects) { + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.refreshInferredProjects(); } } diff --git a/src/server/project.ts b/src/server/project.ts index b6f040b5a26..e3e7de78eef 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -555,6 +555,7 @@ namespace ts.server { * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean { + // TODO: (sheetalkamat) If reload scheduled do that before updating the graph this.lsHost.startRecordingFilesWithChangedResolutions(); let hasChanges = this.updateGraphWorker(); @@ -661,6 +662,7 @@ namespace ts.server { // When a missing file is created, we should update the graph. this.markAsDirty(); + // TODO: (sheetalkamat) schedule the update graph instead of doing it right away this.updateGraph(); } }); diff --git a/src/server/session.ts b/src/server/session.ts index 4b4d0a02265..2656b6a20ba 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -462,6 +462,7 @@ namespace ts.server { private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { this.host.setTimeout(() => { if (matchSeq(seq)) { + // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? this.projectService.refreshInferredProjects(); } }, ms); From e568976239e104e6b678449f9369d2e1ae4bed50 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 12 Jul 2017 23:15:03 -0700 Subject: [PATCH 018/144] Allows the delayed update graph and project structure which helps in batching the updates when there are multiple files added/removed/changed --- .../unittests/tsserverProjectSystem.ts | 30 ++- src/harness/unittests/typingsInstaller.ts | 20 +- src/server/editorServices.ts | 172 ++++++++++-------- src/server/project.ts | 26 ++- src/server/session.ts | 11 +- 5 files changed, 161 insertions(+), 98 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 4426e52ab66..83e92c2018d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -639,6 +639,11 @@ namespace ts.projectSystem { this.timeoutCallbacks.unregister(timeoutId); } + checkTimeoutQueueLengthAndRun(expected: number) { + this.checkTimeoutQueueLength(expected); + this.runQueuedTimeoutCallbacks(); + } + checkTimeoutQueueLength(expected: number) { const callbacksCount = this.timeoutCallbacks.count(); assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`); @@ -899,6 +904,9 @@ namespace ts.projectSystem { // remove the tsconfig file host.reloadFS(filesWithoutConfig); + checkNumberOfInferredProjects(projectService, 1); + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); checkWatchedDirectories(host, ["/a/b", "/a"]); @@ -920,7 +928,7 @@ namespace ts.projectSystem { // add a new ts file host.reloadFS([commonFile1, commonFile2, libFile, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); @@ -1022,12 +1030,12 @@ namespace ts.projectSystem { // delete commonFile2 host.reloadFS([commonFile1, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(project, [commonFile1.path]); // re-add commonFile2 host.reloadFS([commonFile1, commonFile2, configFile]); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); @@ -1086,6 +1094,9 @@ namespace ts.projectSystem { }`; host.reloadFS(files); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects checkNumberOfConfiguredProjects(projectService, 1); checkProjectRootFiles(project, [commonFile1.path]); @@ -1158,6 +1169,7 @@ namespace ts.projectSystem { "files": ["${file1.path}"] }`; host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -1587,7 +1599,7 @@ namespace ts.projectSystem { }; host.reloadFS([file1, modifiedFile2, file3]); - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfInferredProjects(projectService, 1); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); }); @@ -1618,6 +1630,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 1 }); host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 2 }); @@ -1779,7 +1792,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, configFile]); - host.checkTimeoutQueueLength(0); // TODO: update graph scheduling (instead of instant update graph) + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); @@ -1814,6 +1827,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, modifiedConfigFile]); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); }); @@ -1923,7 +1937,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); host.reloadFS([file1, file2]); - + host.checkTimeoutQueueLengthAndRun(1); checkNumberOfProjects(projectService, { inferredProjects: 2 }); checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); @@ -2217,6 +2231,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { inferredProjects: 2 }); projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ModuleResolutionKind.Classic }); + host.checkTimeoutQueueLengthAndRun(3); checkNumberOfProjects(projectService, { inferredProjects: 1 }); }); @@ -2406,7 +2421,7 @@ namespace ts.projectSystem { assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state"); host.reloadFS([f1, f2, configWithExclude]); - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); assert.isTrue(project.languageServiceEnabled, "Language service enabled"); assert.equal(lastEvent.data.project, project, "project"); @@ -2783,6 +2798,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]); host.reloadFS([libES5, libES2015Promise, app, config2]); + host.checkTimeoutQueueLengthAndRun(2); projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]); diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index ceae7582ace..b29a619b1b4 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -138,6 +138,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]); }); @@ -337,6 +338,8 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]); }); @@ -460,6 +463,8 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]); }); @@ -538,7 +543,7 @@ namespace ts.projectSystem { for (const f of typingFiles) { assert.isTrue(host.fileExists(f.path), `expected file ${f.path} to exist`); } - + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path, commander.path, express.path, jquery.path, moment.path, lodash.path]); }); @@ -641,7 +646,7 @@ namespace ts.projectSystem { assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests"); installer.executePendingCommands(); - + host.checkTimeoutQueueLengthAndRun(3); // for 2 projects and 1 refreshing inferred project checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]); checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]); }); @@ -695,6 +700,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -742,6 +748,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -788,6 +795,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); @@ -826,9 +834,10 @@ namespace ts.projectSystem { installer.checkPendingCommands(/*expectedCount*/ 0); host.reloadFS([f, fixedPackageJson]); + host.checkTimeoutQueueLengthAndRun(2); // To refresh the project and refresh inferred projects // expected install request installer.installAll(/*expectedCount*/ 1); - + host.checkTimeoutQueueLengthAndRun(2); service.checkNumberOfProjects({ inferredProjects: 1 }); checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]); }); @@ -950,8 +959,7 @@ namespace ts.projectSystem { endOffset: 0 } }); - host.checkTimeoutQueueLength(1); - host.runQueuedTimeoutCallbacks(); + host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); assert.equal(version1, version2, "set of unresolved imports should not change"); }); @@ -1132,6 +1140,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); assert.isTrue(seenTelemetryEvent); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); }); @@ -1185,6 +1194,7 @@ namespace ts.projectSystem { assert.isTrue(!!endEvent); assert.isTrue(beginEvent.eventId === endEvent.eventId); assert.isTrue(endEvent.installSuccess); + host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9aa627e75a1..6d9be7a6520 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -358,6 +358,8 @@ namespace ts.server { private static safelist: SafeList = defaultTypeSafeList; private changedFiles: ScriptInfo[]; + private pendingProjectUpdates = createMap(); + private pendingInferredProjectUpdate: boolean; readonly toCanonicalFileName: (f: string) => string; @@ -444,8 +446,44 @@ namespace ts.server { break; } project.markAsDirty(); - // TODO: (sheetalkamat) Schedule Update graph instead of immediate - project.updateGraph(); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + } + + private delayInferredProjectsRefresh() { + this.pendingInferredProjectUpdate = true; + this.throttledOperations.schedule("*refreshInferredProjects*", /*delay*/ 250, () => { + if (this.pendingProjectUpdates.size !== 0) { + this.delayInferredProjectsRefresh(); + } + else if (this.pendingInferredProjectUpdate) { + this.pendingInferredProjectUpdate = false; + this.refreshInferredProjects(); + } + }); + } + + private delayUpdateProjectGraph(project: Project) { + const projectName = project.getProjectName(); + this.pendingProjectUpdates.set(projectName, project); + this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { + const project = this.pendingProjectUpdates.get(projectName); + if (project) { + this.pendingProjectUpdates.delete(projectName); + project.updateGraph(); + } + }); + } + + delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { + this.delayUpdateProjectGraph(project); + this.delayInferredProjectsRefresh(); + } + + private delayUpdateProjectGraphs(projects: Project[]) { + for (const project of projects) { + this.delayUpdateProjectGraph(project); + } + this.delayInferredProjectsRefresh(); } setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void { @@ -457,9 +495,9 @@ namespace ts.server { for (const proj of this.inferredProjects) { proj.setCompilerOptions(this.compilerOptionsForInferredProjects); proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave; + proj.markAsDirty(); } - // TODO: (sheetalkamat) (ensure updated projects) - this.updateProjectGraphs(this.inferredProjects); + this.delayUpdateProjectGraphs(this.inferredProjects); } stopWatchingDirectory(directory: string) { @@ -479,14 +517,17 @@ namespace ts.server { getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean) { if (refreshInferredProjects) { - // TODO: (sheetalkamat) needs to update inferred projects too ? this.ensureInferredProjectsUpToDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); return scriptInfo && scriptInfo.getDefaultProject(); } - private ensureInferredProjectsUpToDate() { + /** + * Ensures the project structures are upto date + * @param refreshInferredProjects when true updates the inferred projects even if there is no pending work + */ + private ensureInferredProjectsUpToDate(refreshInferredProjects?: boolean) { if (this.changedFiles) { let projectsToUpdate: Project[]; if (this.changedFiles.length === 1) { @@ -499,10 +540,20 @@ namespace ts.server { projectsToUpdate = projectsToUpdate.concat(f.containingProjects); } } - this.updateProjectGraphs(projectsToUpdate); this.changedFiles = undefined; + this.updateProjectGraphs(projectsToUpdate); + } + + if (this.pendingProjectUpdates.size !== 0) { + const projectsToUpdate = arrayFrom(this.pendingProjectUpdates.values()); + this.pendingProjectUpdates.clear(); + this.updateProjectGraphs(projectsToUpdate); + } + + if (this.pendingInferredProjectUpdate || refreshInferredProjects) { + this.pendingInferredProjectUpdate = false; + this.refreshInferredProjects(); } - // TODO: (sheetalkamat) should also refresh inferred projects here since we are postponing the changes in closed files } private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { @@ -526,15 +577,11 @@ namespace ts.server { } private updateProjectGraphs(projects: Project[]) { - let shouldRefreshInferredProjects = false; for (const p of projects) { if (!p.updateGraph()) { - shouldRefreshInferredProjects = true; + this.pendingInferredProjectUpdate = true; } } - if (shouldRefreshInferredProjects) { - this.refreshInferredProjects(); - } } private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) { @@ -559,7 +606,7 @@ namespace ts.server { // file has been changed which might affect the set of referenced files in projects that include // this file and set of inferred projects info.reloadFromFile(); - this.recordChangedFile(info); + this.delayUpdateProjectGraphs(info.containingProjects); } } } @@ -580,36 +627,28 @@ namespace ts.server { info.detachAllProjects(); - // TODO: (sheetalkamat) Schedule this too ? // update projects to make sure that set of referenced files is correct - this.updateProjectGraphs(containingProjects); + this.delayUpdateProjectGraphs(containingProjects); - if (!this.eventHandler) { - return; - } + // TODO: (sheetalkamat) Someway to send this event so that error checks are updated? + // if (!this.eventHandler) { + // return; + // } - for (const openFile of this.openFiles) { - this.eventHandler({ - eventName: ContextEvent, - data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } - }); - } + // for (const openFile of this.openFiles) { + // this.eventHandler({ + // eventName: ContextEvent, + // data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } + // }); + // } } - - this.printProjects(); } private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { this.logger.info(`Type root file ${fileName} changed`); project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); project.updateTypes(); - // TODO: (sheetalkamat) schedule project update and referesh inferred projects instead - - //this.throttledOperations.schedule(project.getConfigFilePath() + " * type root", /*delay*/ 250, () => { - // this.reloadConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) - project.updateGraph(); - this.refreshInferredProjects(); - //}); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } /** @@ -650,10 +689,7 @@ namespace ts.server { 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(); - } + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { @@ -664,16 +700,14 @@ namespace ts.server { // 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 const openFilesInProject = this.getOrphanFiles(); - // TODO: (sheetalkamat) can this be scheduled too - this.reloadConfiguredProjectForFiles(openFilesInProject); + this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); }); + this.delayInferredProjectsRefresh(); } else { this.logger.info(`Config file changed: ${configFileName}`); - // TODO: (sheetalkamat) This should be scheduled and marked for reload of configured project since we dont want to updating this on every change - this.reloadConfiguredProject(project); - this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); + project.pendingReload = true; + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } - this.refreshInferredProjects(); } /** @@ -698,6 +732,8 @@ namespace ts.server { this.logger.info(`remove project: ${project.getRootFiles().toString()}`); project.close(); + // Remove the project from pending project updates + this.pendingProjectUpdates.delete(project.getProjectName()); switch (project.projectKind) { case ProjectKind.External: @@ -1249,7 +1285,8 @@ namespace ts.server { * Read the config file of the project again and update the project * @param project */ - private reloadConfiguredProject(project: ConfiguredProject) { + /* @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. @@ -1257,7 +1294,8 @@ namespace ts.server { // regardless the "success" here is true or not. const host = project.getCachedServerHost(); host.clearCache(); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath(), host); + const configFileName = project.getConfigFilePath(); + const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); project.configFileSpecs = configFileSpecs; if (!success) { // reset project settings to default @@ -1266,6 +1304,7 @@ namespace ts.server { else { this.updateConfiguredProject(project, projectOptions, configFileErrors); } + this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); } /** @@ -1405,11 +1444,11 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); - this.reloadConfiguredProjectForFiles(this.openFiles); + this.reloadConfiguredProjectForFiles(this.openFiles, project => this.reloadConfiguredProject(project)); this.refreshInferredProjects(); } - reloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) { + reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], reload: (project: ConfiguredProject) => void) { const mapUpdatedProjects = createMap(); // try to reload config file for all open files for (const info of openFiles) { @@ -1425,7 +1464,7 @@ namespace ts.server { mapUpdatedProjects.set(configFileName, true); } else if (!mapUpdatedProjects.has(configFileName)) { - this.reloadConfiguredProject(project); + reload(project); mapUpdatedProjects.set(configFileName, true); } } @@ -1437,7 +1476,7 @@ namespace ts.server { * It is called on the premise that all the configured projects are * up to date. */ - refreshInferredProjects() { + private refreshInferredProjects() { this.logger.info("updating project structure from ..."); this.printProjects(); @@ -1547,17 +1586,6 @@ namespace ts.server { return files; } - private recordChangedFile(scriptInfo: ScriptInfo) { - if (!this.changedFiles) { - this.changedFiles = [scriptInfo]; - } - else if (this.changedFiles.indexOf(scriptInfo) < 0) { - this.changedFiles.push(scriptInfo); - } - // TODO: (sheetalkamat) Schedule the project update graphs - this.updateProjectGraphs(scriptInfo.containingProjects); - } - /* @internal */ applyChangesInOpenFiles(openFiles: protocol.ExternalFile[], changedFiles: protocol.ChangedOpenFile[], closedFiles: string[]): void { if (openFiles) { @@ -1578,7 +1606,12 @@ namespace ts.server { const change = file.changes[i]; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } - this.recordChangedFile(scriptInfo); + if (!this.changedFiles) { + this.changedFiles = [scriptInfo]; + } + else if (this.changedFiles.indexOf(scriptInfo) < 0) { + this.changedFiles.push(scriptInfo); + } } } @@ -1590,8 +1623,7 @@ namespace ts.server { // if files were open or closed then explicitly refresh list of inferred projects // otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update if (openFiles || closedFiles) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } @@ -1614,8 +1646,7 @@ namespace ts.server { } this.externalProjectToConfiguredProjectMap.delete(fileName); if (shouldRefreshInferredProjects && !suppressRefresh) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } else { @@ -1624,8 +1655,7 @@ namespace ts.server { if (externalProject) { this.removeProject(externalProject); if (!suppressRefresh) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } } @@ -1649,8 +1679,7 @@ namespace ts.server { this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); }); - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } /** Makes a filename safe to insert in a RegExp */ @@ -1844,8 +1873,7 @@ namespace ts.server { this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); } if (!suppressRefreshOfInferredProjects) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.refreshInferredProjects(); + this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); } } } diff --git a/src/server/project.ts b/src/server/project.ts index e3e7de78eef..5b78f1f6630 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -303,7 +303,11 @@ namespace ts.server { // if we have a program - release all files that are enlisted in program for (const f of this.program.getSourceFiles()) { const info = this.projectService.getScriptInfo(f.fileName); - info.detachFromProject(this); + // We might not find the script info in case its not associated with the project any more + // and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk) + if (info) { + info.detachFromProject(this); + } } } if (!this.program || !this.languageServiceEnabled) { @@ -555,7 +559,6 @@ namespace ts.server { * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean { - // TODO: (sheetalkamat) If reload scheduled do that before updating the graph this.lsHost.startRecordingFilesWithChangedResolutions(); let hasChanges = this.updateGraphWorker(); @@ -662,8 +665,7 @@ namespace ts.server { // When a missing file is created, we should update the graph. this.markAsDirty(); - // TODO: (sheetalkamat) schedule the update graph instead of doing it right away - this.updateGraph(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } }); this.missingFilesMap.set(missingFilePath, fileWatcher); @@ -980,6 +982,9 @@ namespace ts.server { private typeRootsWatchers: FileWatcher[]; readonly canonicalConfigFilePath: NormalizedPath; + /* @internal */ + pendingReload: boolean; + /*@internal*/ configFileSpecs: ConfigFileSpecs; @@ -1001,6 +1006,19 @@ namespace ts.server { this.enablePlugins(); } + /** + * Checks if the project has reload from disk pending, if thats pending, it reloads (and then updates graph as part of that) instead of just updating the graph + * @returns: true if set of files in the project stays the same and false - otherwise. + */ + updateGraph(): boolean { + if (this.pendingReload) { + this.pendingReload = false; + this.projectService.reloadConfiguredProject(this); + return true; + } + return super.updateGraph(); + } + getCachedServerHost() { return this.lsHost.host as CachedServerHost; } diff --git a/src/server/session.ts b/src/server/session.ts index 2656b6a20ba..a51e17cde65 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -459,15 +459,6 @@ namespace ts.server { } } - private updateProjectStructure(seq: number, matchSeq: (seq: number) => boolean, ms = 1500) { - this.host.setTimeout(() => { - if (matchSeq(seq)) { - // TODO: (sheetalkamat) is this ensureRefereshedProjects instead ? - this.projectService.refreshInferredProjects(); - } - }, ms); - } - private updateErrorCheck(next: NextStep, checkList: PendingErrorCheck[], seq: number, matchSeq: (seq: number) => boolean, ms = 1500, followMs = 200, requireOpen = true) { if (followMs > ms) { followMs = ms; @@ -1302,7 +1293,7 @@ namespace ts.server { scriptInfo.editContent(start, end, args.insertString); this.changeSeq++; } - this.updateProjectStructure(this.changeSeq, n => n === this.changeSeq); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } } From 036590138115509c12f1845711e3034995259cb2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 13 Jul 2017 15:04:49 -0700 Subject: [PATCH 019/144] use single instance of getCanonicalFileName --- src/compiler/commandLineParser.ts | 14 ++++++++------ src/server/editorServices.ts | 7 ++++--- src/server/lsHost.ts | 16 ++++++++-------- src/server/project.ts | 7 +++---- src/server/scriptInfo.ts | 5 ++--- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 159dc27bb47..3b3127f8a41 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1416,7 +1416,8 @@ namespace ts { Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); const errors: Diagnostic[] = []; - const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, getCanonicalFileName, resolutionStack, errors); const { raw } = parsedConfig; const options = extend(existingOptions, parsedConfig.options || {}); options.configFilePath = configFileName; @@ -1525,11 +1526,11 @@ namespace ts { host: ParseConfigHost, basePath: string, configFileName: string, + getCanonicalFileName: (fileName: string) => string, resolutionStack: Path[], errors: Diagnostic[], ): ParsedTsconfig { basePath = normalizeSlashes(basePath); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); if (resolutionStack.indexOf(resolvedPath) >= 0) { @@ -1711,7 +1712,7 @@ namespace ts { const extendedDirname = getDirectoryPath(extendedConfigPath); const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, - getBaseFileName(extendedConfigPath), resolutionStack, errors); + getBaseFileName(extendedConfigPath), getCanonicalFileName, resolutionStack, errors); if (sourceFile) { sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); } @@ -1954,6 +1955,10 @@ namespace ts { basePath = normalizePath(basePath); let validatedIncludeSpecs: string[], validatedExcludeSpecs: string[]; + // The exclude spec list is converted into a regular expression, which allows us to quickly + // test whether a file or directory should be excluded before recursively traversing the + // file system. + if (includeSpecs) { validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); } @@ -1986,9 +1991,6 @@ namespace ts { 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 - // test whether a file or directory should be excluded before recursively traversing the - // file system. const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper; // Literal file names (provided via the "files" array in tsconfig.json) are stored in a diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1b2793e5f2f..db7234c8824 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1198,7 +1198,7 @@ namespace ts.server { } private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { - const cachedServerHost = new CachedServerHost(this.host); + const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName); const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); if (success) { this.logger.info(`Opened configuration file ${configFileName}`); @@ -1366,10 +1366,11 @@ namespace ts.server { } getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) { - let info = this.getScriptInfoForNormalizedPath(fileName); + const path = toPath(fileName, this.host.getCurrentDirectory(), this.toCanonicalFileName); + let info = this.getScriptInfoForPath(path); if (!info) { if (openedByClient || this.host.fileExists(fileName)) { - info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent); + info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path); this.filenameToScriptInfo.set(info.path, info); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index b54f493be1b..a7e9c5e231c 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -11,15 +11,13 @@ namespace ts.server { readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; - private getCanonicalFileName: (fileName: string) => string; private cachedReadDirectoryResult = createMap(); private readonly currentDirectory: string; - constructor(private readonly host: ServerHost) { + constructor(private readonly host: ServerHost, private getCanonicalFileName: (fileName: string) => string) { this.args = host.args; this.newLine = host.newLine; this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; - this.getCanonicalFileName = createGetCanonicalFileName(this.useCaseSensitiveFileNames); if (host.trace) { this.trace = s => host.trace(s); } @@ -193,21 +191,23 @@ namespace ts.server { private compilationSettings: CompilerOptions; private readonly resolvedModuleNames = createMap>(); private readonly resolvedTypeReferenceDirectives = createMap>(); - /* @internal */ - readonly getCanonicalFileName: (fileName: string) => string; private filesWithChangedSetOfUnresolvedImports: Path[]; private resolveModuleName: typeof resolveModuleName; readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; + /** + * This is the host that is associated with the project. This is normally same as projectService's host + * except in Configured projects where it is CachedServerHost so that we can cache the results of the + * file system entries as we would anyways be watching files in the project (so safe to cache) + */ /*@internal*/ host: ServerHost; constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { this.host = host; this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - this.getCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); if (host.trace) { this.trace = s => host.trace(s); @@ -262,7 +262,7 @@ namespace ts.server { getResultFileName: (result: R) => string | undefined, logChanges: boolean): R[] { - const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const path = toPath(containingFile, this.host.getCurrentDirectory(), this.project.projectService.toCanonicalFileName); const currentResolutionsInFile = cache.get(path); const newResolutions: Map = createMap(); @@ -403,7 +403,7 @@ namespace ts.server { fileExists(file: string): boolean { // As an optimization, don't hit the disks for files we already know don't exist // (because we're watching for their creation). - const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const path = toPath(file, this.host.getCurrentDirectory(), this.project.projectService.toCanonicalFileName); return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file); } diff --git a/src/server/project.ts b/src/server/project.ts index 9db74efeb6b..5a397bdf3a4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -495,7 +495,7 @@ namespace ts.server { // add a root file to project addMissingFileRoot(fileName: NormalizedPath) { const path = toPath(fileName, this.projectService.host.getCurrentDirectory(), - createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames)); + this.projectService.toCanonicalFileName); this.rootFilesMap.set(path, fileName); this.markAsDirty(); } @@ -837,11 +837,10 @@ namespace ts.server { } const currentDirectory = getDirectoryPath(path); - const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames); // Handle triple slash references if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, currentDirectory, getCanonicalFileName); + const referencedPath = toPath(referencedFile.fileName, currentDirectory, this.projectService.toCanonicalFileName); referencedFiles.set(referencedPath, true); } } @@ -854,7 +853,7 @@ namespace ts.server { } const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, currentDirectory, getCanonicalFileName); + const typeFilePath = toPath(fileName, currentDirectory, this.projectService.toCanonicalFileName); referencedFiles.set(typeFilePath, true); }); } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 3da4cdb14af..46008e77340 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -148,7 +148,6 @@ namespace ts.server { */ readonly containingProjects: Project[] = []; private formatCodeSettings: FormatCodeSettings; - readonly path: Path; private fileWatcher: FileWatcher; private textStorage: TextStorage; @@ -159,9 +158,9 @@ namespace ts.server { private readonly host: ServerHost, readonly fileName: NormalizedPath, readonly scriptKind: ScriptKind, - public hasMixedContent = false) { + public hasMixedContent: boolean, + readonly path: Path) { - this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames)); this.textStorage = new TextStorage(host, fileName); if (hasMixedContent) { this.textStorage.reload(""); From 404aa8f0be1fdc29fc34d5913628407f402e9af9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 13 Jul 2017 20:08:57 -0700 Subject: [PATCH 020/144] Logging of the watch add/remove/event --- src/server/editorServices.ts | 109 ++++++++++++++---- src/server/project.ts | 210 +++++++++++++++++------------------ src/server/scriptInfo.ts | 15 +-- src/server/utilities.ts | 73 ++++++++++++ 4 files changed, 264 insertions(+), 143 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index db7234c8824..0aed0ab99fa 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -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 { 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) { - 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) { - 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(); } diff --git a/src/server/project.ts b/src/server/project.ts index 5a397bdf3a4..aa492d246dc 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -117,7 +117,7 @@ namespace ts.server { private rootFilesMap: Map = createMap(); private program: Program; private externalFiles: SortedReadonlyArray; - private missingFilesMap: Map = createMap(); + private missingFilesMap: Map; private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap(); private lastCachedUnresolvedImportsList: SortedReadonlyArray; @@ -327,7 +327,9 @@ namespace ts.server { this.lsHost = undefined; // Clean up file watchers waiting for missing files - this.missingFilesMap.forEach(fileWatcher => fileWatcher.close()); + cleanExistingMap(this.missingFilesMap, (missingFilePath, fileWatcher) => { + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose); + }); this.missingFilesMap = undefined; // signal language service to release source files acquired from document registry @@ -639,38 +641,38 @@ namespace ts.server { } const missingFilePaths = this.program.getMissingFilePaths(); - const missingFilePathsSet = arrayToSet(missingFilePaths); + const newMissingFilePathMap = arrayToSet(missingFilePaths); + // Update the missing file paths watcher + this.missingFilesMap = mutateExistingMapWithNewSet( + this.missingFilesMap, newMissingFilePathMap, + // Watch the missing files + missingFilePath => { + const fileWatcher = this.projectService.addFileWatcher( + WatchType.MissingFilePath, this, missingFilePath, + (filename: string, eventKind: FileWatcherEventKind) => { + if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { + this.missingFilesMap.delete(missingFilePath); + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - this.missingFilesMap.forEach((fileWatcher, missingFilePath) => { - if (!missingFilePathsSet.has(missingFilePath)) { - this.missingFilesMap.delete(missingFilePath); - fileWatcher.close(); - } - }); + if (this.projectKind === ProjectKind.Configured) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + } - // Missing files that are not yet watched should be added to the map. - for (const missingFilePath of missingFilePaths) { - if (!this.missingFilesMap.has(missingFilePath)) { - const fileWatcher = this.projectService.host.watchFile(missingFilePath, (filename: string, eventKind: FileWatcherEventKind) => { - if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { - fileWatcher.close(); - this.missingFilesMap.delete(missingFilePath); - - if (this.projectKind === ProjectKind.Configured) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + // When a missing file is created, we should update the graph. + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - - // When a missing file is created, we should update the graph. - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - }); - this.missingFilesMap.set(missingFilePath, fileWatcher); + ); + return fileWatcher; + }, + // Files that are no longer missing (e.g. because they are no longer required) + // should no longer be watched. + (missingFilePath, fileWatcher) => { + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded); } - } + ); } const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray; @@ -694,7 +696,7 @@ namespace ts.server { } isWatchedMissingFile(path: Path) { - return this.missingFilesMap.has(path); + return this.missingFilesMap && this.missingFilesMap.has(path); } getScriptInfoLSHost(fileName: string) { @@ -976,9 +978,10 @@ namespace ts.server { */ export class ConfiguredProject extends Project { private typeAcquisition: TypeAcquisition; - private configFileWatcher: FileWatcher; + /* @internal */ + configFileWatcher: FileWatcher; private directoriesWatchedForWildcards: Map; - private typeRootsWatchers: FileWatcher[]; + private typeRootsWatchers: Map; readonly canonicalConfigFilePath: NormalizedPath; /* @internal */ @@ -1023,7 +1026,7 @@ namespace ts.server { } getConfigFilePath() { - return this.getProjectName(); + return asNormalizedPath(this.getProjectName()); } enablePlugins() { @@ -1131,94 +1134,83 @@ namespace ts.server { })); } - watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) { - this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); - } - - watchTypeRoots(callback: (project: ConfiguredProject, path: NormalizedPath) => void) { - const roots = this.getEffectiveTypeRoots(); - const watchers: FileWatcher[] = []; - for (const root of roots) { - this.projectService.logger.info(`Add type root watcher for: ${root}`); - watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, toNormalizedPath(getNormalizedAbsolutePath(path, root))), /*recursive*/ false)); - } - this.typeRootsWatchers = watchers; - } - - private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) { - if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) { - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`); - this.directoriesWatchedForWildcards.set(directory, { - watcher: this.projectService.host.watchDirectory( - directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toNormalizedPath(getNormalizedAbsolutePath(path, directory))), - recursive - ), - recursive - }); - } - } - watchWildcards(wildcardDirectories: Map) { - if (wildcardDirectories) { - if (this.directoriesWatchedForWildcards) { - this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { - const currentFlag = wildcardDirectories.get(directory); - // Remove already watching wild card if it isnt in updated map - if (currentFlag === undefined) { - this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); - watcher.close(); - this.directoriesWatchedForWildcards.delete(directory); - } - // Or if the recursive doesnt match (add the updated one here) - else { - const currentRecursive = (currentFlag & WatchDirectoryFlags.Recursive) !== 0; - if (currentRecursive !== recursive) { - this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); - watcher.close(); - this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true); - } - } - }); - } - else { - this.directoriesWatchedForWildcards = createMap(); - } - wildcardDirectories.forEach((flag, directory) => - this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false)); - } - else { - this.stopWatchingWildCards(); - } + this.directoriesWatchedForWildcards = mutateExistingMap( + this.directoriesWatchedForWildcards, wildcardDirectories, + // Watcher is same if the recursive flags match + ({ recursive: existingRecursive }, flag) => { + // If the recursive dont match, it needs update + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + return existingRecursive !== recursive; + }, + // Create new watch + (directory, flag) => { + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + return { + watcher: this.projectService.addDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), + recursive + ), + recursive + }; + }, + // Close existing watch thats not needed any more + (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded + ), + // Close existing watch that doesnt match in recursive flag + (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged + ) + ); } - stopWatchingWildCards() { - if (this.directoriesWatchedForWildcards) { - this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { - this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); - watcher.close(); - }); - this.directoriesWatchedForWildcards = undefined; - } + stopWatchingWildCards(reason: WatcherCloseReason) { + cleanExistingMap( + this.directoriesWatchedForWildcards, + (directory, { watcher, recursive }) => + this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this, + directory, watcher, recursive, reason) + ); + this.directoriesWatchedForWildcards = undefined; + } + + watchTypeRoots() { + const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir)); + this.typeRootsWatchers = mutateExistingMapWithNewSet( + this.typeRootsWatchers, newTypeRoots, + // Create new watch + root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root, + path => this.projectService.onTypeRootFileChanged(this, path), /*recursive*/ false + ), + // Close existing watch thats not needed any more + (directory, watcher) => this.projectService.closeDirectoryWatcher( + WatchType.TypeRoot, this, directory, watcher, /*recursive*/ false, WatcherCloseReason.NotNeeded + ) + ); + } + + stopWatchingTypeRoots(reason: WatcherCloseReason) { + cleanExistingMap( + this.typeRootsWatchers, + (directory, watcher) => + this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this, + directory, watcher, /*recursive*/ false, reason) + ); + this.typeRootsWatchers = undefined; } close() { super.close(); if (this.configFileWatcher) { - this.configFileWatcher.close(); + this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); this.configFileWatcher = undefined; } - if (this.typeRootsWatchers) { - for (const watcher of this.typeRootsWatchers) { - watcher.close(); - } - this.typeRootsWatchers = undefined; - } - - this.stopWatchingWildCards(); + this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); + this.stopWatchingWildCards(WatcherCloseReason.ProjectClose); } addOpenRef() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 46008e77340..902e9d0d97e 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -149,7 +149,8 @@ namespace ts.server { readonly containingProjects: Project[] = []; private formatCodeSettings: FormatCodeSettings; - private fileWatcher: FileWatcher; + /* @internal */ + fileWatcher: FileWatcher; private textStorage: TextStorage; private isOpen: boolean; @@ -291,18 +292,6 @@ namespace ts.server { } } - setWatcher(watcher: FileWatcher): void { - this.stopWatcher(); - this.fileWatcher = watcher; - } - - stopWatcher() { - if (this.fileWatcher) { - this.fileWatcher.close(); - this.fileWatcher = undefined; - } - } - getLatestVersion() { return this.textStorage.getVersion(); } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 3949e7b58dd..ce49fcdb9bf 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -226,6 +226,79 @@ namespace ts.server { } } + export function cleanExistingMap( + existingMap: Map, + onDeleteExistingValue: (key: string, existingValue: T) => void) { + if (existingMap) { + // Remove all + existingMap.forEach((existingValue, key) => { + existingMap.delete(key); + onDeleteExistingValue(key, existingValue); + }); + } + } + + export function mutateExistingMapWithNewSet( + existingMap: Map, newMap: Map, + createNewValue: (key: string) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void + ): Map { + return mutateExistingMap( + existingMap, newMap, + // Same value if the value is set in the map + /*isSameValue*/(_existingValue, _valueInNewMap) => true, + /*createNewValue*/(key, _valueInNewMap) => createNewValue(key), + onDeleteExistingValue, + // Should never be called since we say yes to same values all the time + /*OnDeleteExistingMismatchValue*/(_key, _existingValue) => notImplemented() + ); + } + + export function mutateExistingMap( + existingMap: Map, newMap: Map, + isSameValue: (existingValue: T, valueInNewMap: U) => boolean, + createNewValue: (key: string, valueInNewMap: U) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void, + OnDeleteExistingMismatchValue: (key: string, existingValue: T) => void + ): Map { + // If there are new values update them + if (newMap) { + if (existingMap) { + // Needs update + existingMap.forEach((existingValue, key) => { + const valueInNewMap = newMap.get(key); + // Existing value - remove it + if (valueInNewMap === undefined) { + existingMap.delete(key); + onDeleteExistingValue(key, existingValue); + } + // different value - remove it + else if (!isSameValue(existingValue, valueInNewMap)) { + existingMap.delete(key); + OnDeleteExistingMismatchValue(key, existingValue); + } + }); + } + else { + // Create new + existingMap = createMap(); + } + + // Add new values that are not already present + newMap.forEach((valueInNewMap, key) => { + if (!existingMap.has(key)) { + // New values + existingMap.set(key, createNewValue(key, valueInNewMap)); + } + }); + + return existingMap; + } + + cleanExistingMap(existingMap, onDeleteExistingValue); + return undefined; + } + export class ThrottledOperations { private pendingTimeouts: Map = createMap(); constructor(private readonly host: ServerHost) { From 71d79c62d055a288a9766f86069d82549275023e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 14 Jul 2017 22:35:07 -0700 Subject: [PATCH 021/144] Some refactoring to combine files removal from inferred project --- src/server/editorServices.ts | 193 +++++++++++++++-------------------- src/server/lsHost.ts | 20 ++-- src/server/project.ts | 15 ++- 3 files changed, 105 insertions(+), 123 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0aed0ab99fa..abd8618b060 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -245,20 +245,6 @@ namespace ts.server { } } - /** - * TODO: enforce invariants: - * - 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 - */ - function isRootFileInInferredProject(info: ScriptInfo): boolean { - if (info.containingProjects.length === 0) { - return false; - } - return info.containingProjects[0].projectKind === ProjectKind.Inferred && info.containingProjects[0].isRoot(info); - } - class DirectoryWatchers { /** * a path to directory watcher map that detects added tsconfig files @@ -386,6 +372,7 @@ namespace ts.server { private pendingProjectUpdates = createMap(); private pendingInferredProjectUpdate: boolean; + readonly currentDirectory: string; readonly toCanonicalFileName: (f: string) => string; public readonly host: ServerHost; @@ -417,6 +404,7 @@ namespace ts.server { Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); + this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.directoryWatchers = new DirectoryWatchers(this); this.throttledOperations = new ThrottledOperations(this.host); @@ -431,7 +419,11 @@ namespace ts.server { extraFileExtensions: [] }; - this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.host.getCurrentDirectory()); + this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory); + } + + toPath(fileName: string, basePath = this.currentDirectory) { + return toPath(fileName, basePath, this.toCanonicalFileName); } /* @internal */ @@ -721,7 +713,7 @@ namespace ts.server { 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 - const openFilesInProject = this.getOrphanFiles(); + const openFilesInProject = filter(this.openFiles, file => file.containingProjects.length === 0); this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); }); this.delayInferredProjectsRefresh(); } @@ -762,6 +754,7 @@ namespace ts.server { this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); break; case ProjectKind.Configured: + // Update the map of mapOfKnownTsConfigFiles removeItemFromSet(this.configuredProjects, project); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); break; @@ -772,59 +765,38 @@ namespace ts.server { } private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean): void { - const externalProject = this.findContainingExternalProject(info.fileName); - if (externalProject) { - // file is already included in some external project - do nothing - if (addToListOfOpenFiles) { - this.openFiles.push(info); - } - return; - } - - let foundConfiguredProject = false; - for (const p of info.containingProjects) { - // file is the part of configured project - if (p.projectKind === ProjectKind.Configured) { - foundConfiguredProject = true; - if (addToListOfOpenFiles) { - ((p)).addOpenRef(); - } - } - } - if (foundConfiguredProject) { - if (addToListOfOpenFiles) { - this.openFiles.push(info); - } - return; - } - if (info.containingProjects.length === 0) { // create new inferred project p with the newly opened file as root // or add root to existing inferred project if 'useOneInferredProject' is true - const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info); - if (!this.useSingleInferredProject) { - // if useOneInferredProject is not set then try to fixup ownership of open files - // check 'defaultProject !== inferredProject' is necessary to handle cases - // when creation inferred project for some file has added other open files into this project (i.e. as referenced files) - // we definitely don't want to delete the project that was just created - for (const f of this.openFiles) { - if (f.containingProjects.length === 0 || !inferredProject.containsScriptInfo(f)) { - // this is orphaned file that we have not processed yet - skip it - continue; - } + this.createInferredProjectWithRootFileIfNecessary(info); - for (const fContainingProject of f.containingProjects) { - if (fContainingProject.projectKind === ProjectKind.Inferred && - fContainingProject.isRoot(f) && - fContainingProject !== inferredProject) { - - // open file used to be root in inferred project, - // this inferred project is different from the one we've just created for current file - // and new inferred project references this open file. - // We should delete old inferred project and attach open file to the new one - this.removeProject(fContainingProject); - f.attachToProject(inferredProject); - } + // if useOneInferredProject is not set then try to fixup ownership of open files + // check 'defaultProject !== inferredProject' is necessary to handle cases + // when creation inferred project for some file has added other open files into this project + // (i.e.as referenced files) + // we definitely don't want to delete the project that was just created + // Also note that we need to create a copy of the array since the list of project will change + for (const inferredProject of this.inferredProjects.slice(0, this.inferredProjects.length - 1)) { + Debug.assert(!this.useSingleInferredProject); + // Remove this file from the root of inferred project if its part of more than 2 projects + // This logic is same as iterating over all open files and calling + // this.removRootOfInferredProjectIfNowPartOfOtherProject(f); + // Since this is also called from refreshInferredProject and closeOpen file + // to update inferred projects of the open file, this iteration might be faster + // instead of scanning all open files + const root = inferredProject.getRootScriptInfos(); + Debug.assert(root.length === 1); + if (root[0].containingProjects.length > 1) { + this.removeProject(inferredProject); + } + } + } + else { + for (const p of info.containingProjects) { + // file is the part of configured project + if (p.projectKind === ProjectKind.Configured) { + if (addToListOfOpenFiles) { + ((p)).addOpenRef(); } } } @@ -835,17 +807,6 @@ namespace ts.server { } } - private getOrphanFiles() { - let orphanFiles: ScriptInfo[]; - // for all open files - for (const f of this.openFiles) { - if (f.containingProjects.length === 0) { - (orphanFiles || (orphanFiles = [])).push(f); - } - } - return orphanFiles; - } - /** * Remove this file from the set of open, non-configured files. * @param info The file that has been closed or newly configured @@ -871,8 +832,15 @@ namespace ts.server { } } else if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) { - // open file in inferred project - (projectsToRemove || (projectsToRemove = [])).push(p); + // If this was the open root file of inferred project + if ((p as InferredProject).isProjectWithSingleRoot()) { + // - when useSingleInferredProject is not set, we can guarantee that this will be the only root + // - other wise remove the project if it is the only root + (projectsToRemove || (projectsToRemove = [])).push(p); + } + else { + p.removeFile(info); + } } if (!p.languageServiceEnabled) { @@ -888,11 +856,10 @@ namespace ts.server { } // collect orphanted files and try to re-add them as newly opened - const orphanFiles = this.getOrphanFiles(); - // treat orphaned files as newly opened - if (orphanFiles) { - for (const f of orphanFiles) { + // for all open files + for (const f of this.openFiles) { + if (f.containingProjects.length === 0) { this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false); } } @@ -1003,9 +970,9 @@ namespace ts.server { private findConfiguredProjectByProjectName(configFileName: NormalizedPath) { // make sure that casing of config file name is consistent - configFileName = asNormalizedPath(this.toCanonicalFileName(configFileName)); + const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName)); for (const proj of this.configuredProjects) { - if (proj.canonicalConfigFilePath === configFileName) { + if (proj.canonicalConfigFilePath === canonicalConfigFilePath) { return proj; } } @@ -1186,7 +1153,6 @@ namespace ts.server { languageServiceEnabled, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, cachedServerHost); - this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); project.configFileSpecs = configFileSpecs; project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, @@ -1197,6 +1163,8 @@ namespace ts.server { project.watchTypeRoots(); } + this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); + this.configuredProjects.push(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; @@ -1242,7 +1210,7 @@ namespace ts.server { let scriptInfo: ScriptInfo | NormalizedPath; let path: Path; if (!project.lsHost.fileExists(newRootFile)) { - path = normalizedPathToPath(normalizedPath, this.host.getCurrentDirectory(), this.toCanonicalFileName); + path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName); const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { project.removeFile(existingValue); @@ -1257,16 +1225,12 @@ namespace ts.server { path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { - if (scriptInfo.isScriptOpen() && isRootFileInInferredProject(scriptInfo)) { + project.addRoot(scriptInfo); + if (scriptInfo.isScriptOpen()) { // if file is already root in some inferred project // - remove the file from that project and delete the project if necessary - const inferredProject = scriptInfo.containingProjects[0]; - inferredProject.removeFile(scriptInfo); - if (!inferredProject.hasRoots()) { - this.removeProject(inferredProject); - } + this.removRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo); } - project.addRoot(scriptInfo); } } newRootScriptInfoMap.set(path, scriptInfo); @@ -1362,7 +1326,6 @@ namespace ts.server { root.fileName, project, fileName => this.onConfigFileAddedForInferredProject(fileName)); - project.updateGraph(); if (!useExistingProject) { @@ -1403,7 +1366,7 @@ namespace ts.server { } getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) { - const path = toPath(fileName, this.host.getCurrentDirectory(), this.toCanonicalFileName); + const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { if (openedByClient || this.host.fileExists(fileName)) { @@ -1438,7 +1401,7 @@ namespace ts.server { } getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.host.getCurrentDirectory(), this.toCanonicalFileName)); + return this.getScriptInfoForPath(normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName)); } getScriptInfoForPath(fileName: Path) { @@ -1539,6 +1502,26 @@ namespace ts.server { } } + /** + * - 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 + */ + private removRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { + if (info.containingProjects.length > 1 && + info.containingProjects[0].projectKind === ProjectKind.Inferred && + info.containingProjects[0].isRoot(info)) { + const inferredProject = info.containingProjects[0] as InferredProject; + if (inferredProject.isProjectWithSingleRoot()) { + this.removeProject(inferredProject); + } + else { + inferredProject.removeFile(info); + } + } + } + /** * This function is to update the project structure for every projects. * It is called on the premise that all the configured projects are @@ -1548,26 +1531,16 @@ namespace ts.server { this.logger.info("updating project structure from ..."); this.printProjects(); - const orphantedFiles: ScriptInfo[] = []; - // collect all orphanted script infos from open files for (const info of this.openFiles) { + // collect all orphanted script infos from open files if (info.containingProjects.length === 0) { - orphantedFiles.push(info); + this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ false); } + // Or remove the root of inferred project if is referenced in more than one projects else { - if (isRootFileInInferredProject(info) && info.containingProjects.length > 1) { - const inferredProject = info.containingProjects[0]; - Debug.assert(inferredProject.projectKind === ProjectKind.Inferred); - inferredProject.removeFile(info); - if (!inferredProject.hasRoots()) { - this.removeProject(inferredProject); - } - } + this.removRootOfInferredProjectIfNowPartOfOtherProject(info); } } - for (const f of orphantedFiles) { - this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false); - } for (const p of this.inferredProjects) { p.updateGraph(); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index a7e9c5e231c..1be0f203206 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -27,8 +27,12 @@ namespace ts.server { this.currentDirectory = this.host.getCurrentDirectory(); } + private toPath(fileName: string) { + return toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + } + private getFileSystemEntries(rootDir: string) { - const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(rootDir); const cachedResult = this.cachedReadDirectoryResult.get(path); if (cachedResult) { return cachedResult; @@ -45,7 +49,7 @@ namespace ts.server { private canWorkWithCacheForDir(rootDir: string) { // Some of the hosts might not be able to handle read directory or getDirectories - const path = toPath(rootDir, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(rootDir); if (this.cachedReadDirectoryResult.get(path)) { return true; } @@ -62,7 +66,7 @@ namespace ts.server { } writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(fileName); const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); const baseFileName = getBaseFileName(toNormalizedPath(fileName)); if (result) { @@ -111,14 +115,14 @@ namespace ts.server { } fileExists(fileName: string): boolean { - const path = toPath(fileName, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(fileName); const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); const baseName = getBaseFileName(toNormalizedPath(fileName)); return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName); } directoryExists(dirPath: string) { - const path = toPath(dirPath, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(dirPath); return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath); } @@ -147,7 +151,7 @@ namespace ts.server { } addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) { - const path = toPath(fileOrFolder, this.currentDirectory, this.getCanonicalFileName); + const path = this.toPath(fileOrFolder); const existingResult = this.cachedReadDirectoryResult.get(path); if (existingResult) { if (!this.host.directoryExists(fileOrFolder)) { @@ -262,7 +266,7 @@ namespace ts.server { getResultFileName: (result: R) => string | undefined, logChanges: boolean): R[] { - const path = toPath(containingFile, this.host.getCurrentDirectory(), this.project.projectService.toCanonicalFileName); + const path = this.project.projectService.toPath(containingFile); const currentResolutionsInFile = cache.get(path); const newResolutions: Map = createMap(); @@ -403,7 +407,7 @@ namespace ts.server { fileExists(file: string): boolean { // As an optimization, don't hit the disks for files we already know don't exist // (because we're watching for their creation). - const path = toPath(file, this.host.getCurrentDirectory(), this.project.projectService.toCanonicalFileName); + const path = this.project.projectService.toPath(file); return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file); } diff --git a/src/server/project.ts b/src/server/project.ts index aa492d246dc..1b99e76545e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -496,8 +496,7 @@ namespace ts.server { // add a root file to project addMissingFileRoot(fileName: NormalizedPath) { - const path = toPath(fileName, this.projectService.host.getCurrentDirectory(), - this.projectService.toCanonicalFileName); + const path = this.projectService.toPath(fileName); this.rootFilesMap.set(path, fileName); this.markAsDirty(); } @@ -842,7 +841,7 @@ namespace ts.server { // Handle triple slash references if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, currentDirectory, this.projectService.toCanonicalFileName); + const referencedPath = this.projectService.toPath(referencedFile.fileName, currentDirectory); referencedFiles.set(referencedPath, true); } } @@ -855,7 +854,7 @@ namespace ts.server { } const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, currentDirectory, this.projectService.toCanonicalFileName); + const typeFilePath = this.projectService.toPath(fileName, currentDirectory); referencedFiles.set(typeFilePath, true); }); } @@ -943,6 +942,12 @@ namespace ts.server { } } + isProjectWithSingleRoot() { + // - when useSingleInferredProject is not set, we can guarantee that this will be the only root + // - other wise it has single root if it has single root script info + return !this.projectService.useSingleInferredProject || this.getRootScriptInfos().length === 1; + } + getProjectRootPath() { // Single inferred project does not have a project root. if (this.projectService.useSingleInferredProject) { @@ -1143,7 +1148,7 @@ namespace ts.server { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; return existingRecursive !== recursive; }, - // Create new watch + // Create new watch and recursive info (directory, flag) => { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; return { From f12980dd208b1c375d46053a400baab743811c58 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 15 Jul 2017 13:37:22 -0700 Subject: [PATCH 022/144] Remove the duplcate error about no input files found --- src/compiler/diagnosticMessages.json | 4 - .../unittests/tsserverProjectSystem.ts | 9 +- src/server/editorServices.ts | 146 +++++++----------- 3 files changed, 58 insertions(+), 101 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cf198ec06ec..d5ac603bf1b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3062,10 +3062,6 @@ "category": "Message", "code": 6128 }, - "The config file '{0}' found doesn't contain any source files.": { - "category": "Error", - "code": 6129 - }, "Resolving real path for '{0}', result '{1}'.": { "category": "Message", "code": 6130 diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 273f7dd1534..2272102924c 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -817,18 +817,19 @@ namespace ts.projectSystem { const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); const service = createProjectService(host); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); service.openExternalProject({ projectFileName: "/a/b/project.csproj", - rootFiles: toExternalFiles([f1.path, combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path))]), + rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), options: {} }); service.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], []); + checkProjectActualFiles(service.configuredProjects[0], [upperCaseConfigFilePath]); service.openClientFile(f1.path); service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], []); + checkProjectActualFiles(service.configuredProjects[0], [upperCaseConfigFilePath]); checkProjectActualFiles(service.inferredProjects[0], [f1.path]); }); @@ -3495,7 +3496,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 1); const configuredProject = projectService.configuredProjects[0]; - assert.isTrue(configuredProject.getFileNames().length === 0); + checkProjectActualFiles(configuredProject, [configFile.path]); const inferredProject = projectService.inferredProjects[0]; assert.isTrue(inferredProject.containsFile(file1.path)); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index abd8618b060..122f88d0675 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -363,6 +363,7 @@ namespace ts.server { private compileOnSaveForInferredProjects: boolean; private readonly projectToSizeMap: Map = createMap(); private readonly directoryWatchers: DirectoryWatchers; + private readonly mapOfTsConfigPresence: Map; private readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; @@ -407,6 +408,7 @@ namespace ts.server { this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.directoryWatchers = new DirectoryWatchers(this); + this.mapOfTsConfigPresence = createMap(); this.throttledOperations = new ThrottledOperations(this.host); this.typingsInstaller.attach(this); @@ -661,7 +663,6 @@ namespace ts.server { /* @internal */ onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { - this.logger.info(`Type root file ${fileName} changed`); project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); project.updateTypes(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); @@ -675,34 +676,25 @@ namespace ts.server { /* @internal */ onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: NormalizedPath) { project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); + const configFilename = project.getConfigFilePath(); // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileName}`); return; } - const configFilename = project.getConfigFilePath(); - this.logger.info(`Project: ${configFilename} Detected source file add/remove: ${fileName}`); - const configFileSpecs = project.configFileSpecs; const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), 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)); - } - } + const isErrorNoInputFiles = (error: Diagnostic) => error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; + if (result.fileNames.length !== 0) { + filterMutate(errors, error => !isErrorNoInputFiles(error)); } - 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); + else if (!configFileSpecs.filesSpecs && !some(errors, isErrorNoInputFiles)) { + errors.push(getErrorForNoInputFiles(configFileSpecs, configFilename)); } this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); @@ -888,6 +880,15 @@ namespace ts.server { }); } + private configFileExists(configFileName: NormalizedPath) { + const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); + const cachedResult = this.mapOfTsConfigPresence.get(canonicalConfigFilePath); + if (cachedResult) { + // Use the information here to answer the question + } + return this.host.fileExists(configFileName); + } + /** * This function tries to search for a tsconfig.json for the given file. * This is different from the method the compiler uses because @@ -897,34 +898,20 @@ namespace ts.server { * the newly opened file. */ private getConfigFileNameForFile(fileName: NormalizedPath, projectRootPath?: NormalizedPath) { - const searchPath = getDirectoryPath(fileName); + let searchPath = getDirectoryPath(fileName); this.logger.info(`Search path: ${searchPath}`); // check if this file is already included in one of external projects - const configFileName = this.findConfigFile(asNormalizedPath(searchPath), projectRootPath); - if (configFileName) { - this.logger.info(`Config file name: ${configFileName}`); - } - else { - this.logger.info("No config files found."); - } - return configFileName; - } - - // This is different from the method the compiler uses because - // 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 findConfigFile(searchPath: NormalizedPath, projectRootPath?: NormalizedPath): NormalizedPath { while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) { const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); - if (this.host.fileExists(tsconfigFileName)) { + if (this.configFileExists(tsconfigFileName)) { + this.logger.info(`Config file name: ${tsconfigFileName}`); return tsconfigFileName; } const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); - if (this.host.fileExists(jsconfigFileName)) { + if (this.configFileExists(jsconfigFileName)) { + this.logger.info(`Config file name: ${jsconfigFileName}`); return jsconfigFileName; } @@ -934,6 +921,7 @@ namespace ts.server { } searchPath = parentPath; } + this.logger.info("No config files found."); return undefined; } @@ -982,7 +970,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1007,38 +995,19 @@ namespace ts.server { Debug.assert(!!parsedCommandLine.fileNames); - let success: boolean; - let projectOptions: ProjectOptions; + const projectOptions: 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 + }; - if (parsedCommandLine.fileNames.length === 0) { - errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename)); - 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 - }; - } - - return { success, projectOptions, configFileErrors: errors, configFileSpecs: parsedCommandLine.configFileSpecs }; + return { projectOptions, configFileErrors: errors, configFileSpecs: parsedCommandLine.configFileSpecs }; } private exceededTotalSizeLimitForNonTsFiles(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader) { @@ -1131,17 +1100,6 @@ namespace ts.server { } } - private reportConfigFileDiagnostics(configFileName: string, diagnostics: Diagnostic[], triggerFile: string) { - if (!this.eventHandler) { - return; - } - - this.eventHandler({ - eventName: ConfigFileDiagEvent, - data: { configFileName, diagnostics: diagnostics || [], triggerFile } - }); - } - private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) { const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( @@ -1155,6 +1113,7 @@ namespace ts.server { cachedServerHost); project.configFileSpecs = configFileSpecs; + // TODO: (sheetalkamat) We should also watch the configFiles that are extended project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind) ); @@ -1192,11 +1151,8 @@ namespace ts.server { private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); - if (success) { - this.logger.info(`Opened configuration file ${configFileName}`); - } - + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); + this.logger.info(`Opened configuration file ${configFileName}`); return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName); } @@ -1284,16 +1240,19 @@ namespace ts.server { const host = project.getCachedServerHost(); host.clearCache(); const configFileName = project.getConfigFilePath(); - const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); + this.logger.info(`Reloading configured project ${configFileName}`); + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); project.configFileSpecs = configFileSpecs; - if (!success) { - // reset project settings to default - this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors); + this.updateConfiguredProject(project, projectOptions, configFileErrors); + + if (!this.eventHandler) { + return; } - else { - this.updateConfiguredProject(project, projectOptions, configFileErrors); - } - this.reportConfigFileDiagnostics(configFileName, project.getGlobalProjectErrors(), /*triggerFile*/ configFileName); + + this.eventHandler({ + eventName: ConfigFileDiagEvent, + data: { configFileName, diagnostics: project.getGlobalProjectErrors() || [], triggerFile: configFileName } + }); } /** @@ -1528,7 +1487,7 @@ namespace ts.server { * up to date. */ private refreshInferredProjects() { - this.logger.info("updating project structure from ..."); + this.logger.info("refreshInferredProjects: updating project structure from ..."); this.printProjects(); for (const info of this.openFiles) { @@ -1546,6 +1505,7 @@ namespace ts.server { p.updateGraph(); } + this.logger.info("refreshInferredProjects: updated project structure ..."); this.printProjects(); } From 00011a52af387cabeaddb261ca426529f1cdbe5a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 15 Jul 2017 14:24:44 -0700 Subject: [PATCH 023/144] Refactor root files addition/update for non inferred project --- src/server/editorServices.ts | 39 +++++++++--------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 122f88d0675..410872e369e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -696,7 +696,7 @@ namespace ts.server { else if (!configFileSpecs.filesSpecs && !some(errors, isErrorNoInputFiles)) { errors.push(getErrorForNoInputFiles(configFileSpecs, configFilename)); } - this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader); + this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -1050,7 +1050,7 @@ namespace ts.server { /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); - this.addFilesToProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined); + this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition, /*configFileErrors*/ undefined); this.externalProjects.push(project); this.sendProjectTelemetry(project.externalProjectName, project); return project; @@ -1122,30 +1122,17 @@ namespace ts.server { project.watchTypeRoots(); } - this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); - + this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); this.configuredProjects.push(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; } - private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { - for (const f of files) { - const rootFilename = propertyReader.getFileName(f); - const scriptKind = propertyReader.getScriptKind(f); - const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - const fileName = toNormalizedPath(rootFilename); - if (project.lsHost.fileExists(rootFilename)) { - const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ clientFileName === rootFilename, /*fileContent*/ undefined, scriptKind, hasMixedContent); - project.addRoot(info); - } - else { - // Create the file root with just the filename so that LS will have correct set of roots - project.addMissingFileRoot(fileName); - } - } + private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { project.setProjectErrors(configFileErrors); + this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName); project.setTypeAcquisition(typeAcquisition); + // This doesnt need scheduling since its either creation or reload of the project project.updateGraph(); } @@ -1156,7 +1143,7 @@ namespace ts.server { return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName); } - private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader) { + private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, clientFileName?: string) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap: Map = createMap(); @@ -1170,14 +1157,14 @@ namespace ts.server { const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { project.removeFile(existingValue); - projectRootFilesMap.set(path, normalizedPath); } + projectRootFilesMap.set(path, normalizedPath); scriptInfo = normalizedPath; } else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1211,19 +1198,13 @@ namespace ts.server { } private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { - this.updateNonInferredProjectFiles(project, newUncheckedFiles, propertyReader); project.setCompilerOptions(newOptions); - project.setTypeAcquisition(newTypeAcquisition); - // VS only set the CompileOnSaveEnabled option in the request if the option was changed recently // therefore if it is undefined, it should not be updated. if (compileOnSave !== undefined) { project.compileOnSaveEnabled = compileOnSave; } - project.setProjectErrors(configFileErrors); - - // This doesnt need scheduling since its either creation or reload of the project - project.updateGraph(); + this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, /*clientFileName*/ undefined, newTypeAcquisition, configFileErrors); } /** From 0572b15adc2b3c19ff2912ff7510a0d700d85f06 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 15 Jul 2017 19:11:27 -0700 Subject: [PATCH 024/144] Instead of watching directories, watch tsconfig files of inferred project root --- .../unittests/tsserverProjectSystem.ts | 15 +- src/server/editorServices.ts | 288 ++++++++++++------ src/server/project.ts | 13 +- 3 files changed, 215 insertions(+), 101 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 2272102924c..22bf06bef65 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -281,7 +281,7 @@ namespace ts.projectSystem { } export function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map`); + assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`); for (const name of expectedKeys) { assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } @@ -800,7 +800,7 @@ namespace ts.projectSystem { const project = projectService.inferredProjects[0]; checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - checkWatchedDirectories(host, ["/a/b/c", "/a/b", "/a"]); + checkWatchedFiles(host, ["/a/b/c/tsconfig.json", "/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path, moduleFile.path]); }); it("can handle tsconfig file name with difference casing", () => { @@ -890,15 +890,15 @@ namespace ts.projectSystem { projectService.openClientFile(commonFile2.path); checkNumberOfInferredProjects(projectService, 2); - checkWatchedDirectories(host, ["/a/b", "/a"]); + checkWatchedFiles(host, [configFile.path, "/a/tsconfig.json", libFile.path]); // Add a tsconfig file host.reloadFS(filesWithConfig); - + host.checkTimeoutQueueLengthAndRun(1); checkNumberOfInferredProjects(projectService, 1); checkNumberOfConfiguredProjects(projectService, 1); // watching all files except one that was open - checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedFiles(host, [libFile.path, configFile.path, "/a/tsconfig.json"]); // remove the tsconfig file host.reloadFS(filesWithoutConfig); @@ -908,7 +908,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); - checkWatchedDirectories(host, ["/a/b", "/a"]); + checkWatchedFiles(host, ["/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path]); }); it("add new files to a configured project without file list", () => { @@ -1315,7 +1315,7 @@ namespace ts.projectSystem { host.reloadFS([file1, configFile, file2, file3, libFile]); - + host.checkTimeoutQueueLengthAndRun(1); checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 1); checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); @@ -1728,6 +1728,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); host.reloadFS([file1, file2, file3, configFile]); + host.checkTimeoutQueueLengthAndRun(1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 410872e369e..698dbe3ffd8 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -245,57 +245,14 @@ namespace ts.server { } } - class DirectoryWatchers { - /** - * a path to directory watcher map that detects added tsconfig files - */ - private readonly directoryWatchersForTsconfig: Map = createMap(); - /** - * count of how many projects are using the directory watcher. - * If the number becomes 0 for a watcher, then we should close it. - */ - private readonly directoryWatchersRefCount: Map = createMap(); - - constructor(private readonly projectService: ProjectService) { - } - - stopWatchingDirectory(directory: string) { - // if the ref count for this directory watcher drops to 0, it's time to close it - const refCount = this.directoryWatchersRefCount.get(directory) - 1; - this.directoryWatchersRefCount.set(directory, refCount); - if (refCount === 0) { - this.projectService.logger.info(`Close directory watcher for: ${directory}`); - this.directoryWatchersForTsconfig.get(directory).close(); - this.directoryWatchersForTsconfig.delete(directory); - } - } - - startWatchingContainingDirectoriesForFile(fileName: string, project: InferredProject, callback: (fileName: string) => void) { - let currentPath = getDirectoryPath(fileName); - let parentPath = getDirectoryPath(currentPath); - while (currentPath !== parentPath) { - if (!this.directoryWatchersForTsconfig.has(currentPath)) { - this.projectService.logger.info(`Add watcher for: ${currentPath}`); - this.directoryWatchersForTsconfig.set(currentPath, this.projectService.host.watchDirectory(currentPath, callback)); - this.directoryWatchersRefCount.set(currentPath, 1); - } - else { - this.directoryWatchersRefCount.set(currentPath, this.directoryWatchersRefCount.get(currentPath) + 1); - } - project.directoriesWatchedForTsconfig.push(currentPath); - currentPath = parentPath; - parentPath = getDirectoryPath(parentPath); - } - } - } - /* @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" + ClosedScriptInfo = "Closed Script info", + ConfigFileForInferredRoot = "Config file for the root script info of the inferred project" } /* @internal */ @@ -308,12 +265,27 @@ namespace ts.server { 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" + FileOpened = "File opened", + ConfigProjectCreated = "Config file project created" + } + + const enum ConfigFileWatcherStatus { + ReloadingFiles = "Reloading configured projects files", + NoAction = "No action on files", + UpdatedCallback = "Updated the callback", + TrackingFileAdded = "Tracking file added", + TrackingFileRemoved = "Tracking file removed" } /* @internal */ export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; + type ConfigFileExistence = { + exists: boolean; + trackingOpenFiles?: ScriptInfo[]; + configFileWatcher?: FileWatcher; + }; + export interface ProjectServiceOptions { host: ServerHost; logger: Logger; @@ -362,8 +334,7 @@ namespace ts.server { private compilerOptionsForInferredProjects: CompilerOptions; private compileOnSaveForInferredProjects: boolean; private readonly projectToSizeMap: Map = createMap(); - private readonly directoryWatchers: DirectoryWatchers; - private readonly mapOfTsConfigPresence: Map; + private readonly mapOfConfigFilePresence: Map; private readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; @@ -407,8 +378,7 @@ namespace ts.server { this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); - this.directoryWatchers = new DirectoryWatchers(this); - this.mapOfTsConfigPresence = createMap(); + this.mapOfConfigFilePresence = createMap(); this.throttledOperations = new ThrottledOperations(this.host); this.typingsInstaller.attach(this); @@ -519,10 +489,6 @@ namespace ts.server { this.delayUpdateProjectGraphs(this.inferredProjects); } - stopWatchingDirectory(directory: string) { - this.directoryWatchers.stopWatchingDirectory(directory); - } - findProject(projectName: string): Project { if (projectName === undefined) { return undefined; @@ -701,36 +667,53 @@ namespace ts.server { } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath); if (eventKind === FileWatcherEventKind.Deleted) { + // Update the cached status + // No action needed on tracking open files since the existing config file anyways didnt affect the tracking file + configFilePresenceInfo.exists = false; + this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.NoAction); 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 - const openFilesInProject = filter(this.openFiles, file => file.containingProjects.length === 0); - this.reloadConfiguredProjectForFiles(openFilesInProject, project => { project.pendingReload = true; this.delayUpdateProjectGraph(project); }); - this.delayInferredProjectsRefresh(); + const orphanFiles = filter(this.openFiles, file => file.containingProjects.length === 0); + this.delayReloadConfiguredProjectForFiles(orphanFiles); } else { project.pendingReload = true; - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + if (configFilePresenceInfo.trackingOpenFiles) { + this.delayUpdateProjectGraph(project); + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles); + } + else { + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + } } } /** - * This is the callback function when a watched directory has an added tsconfig file. + * This is the callback function for the config file add/remove/change for the root in the inferred project */ - private onConfigFileAddedForInferredProject(fileName: string) { - // TODO: check directory separators - if (getBaseFileName(fileName) !== "tsconfig.json") { - this.logger.info(`${fileName} is not tsconfig.json`); - return; - } + private onConfigFileAddedForInferredProject(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) { + // This callback is called only if we dont have config file project for this config file + const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath); - // TODO: (sheetalkamat) - // 1. We should only watch tsconfig/jsconfig file here instead of watching directory - // 2. We should try reloading projects with open files in Inferred project only - // 3. We should use this watcher to answer questions to findConfigFile rather than calling host everytime - this.logger.info(`Detected newly added tsconfig file: ${fileName}`); - this.reloadProjects(); + if (eventKind === FileWatcherEventKind.Deleted) { + // No action needed if the event was for deletion of the file + // - because the existing config file didnt affect the inferred project roots anyways + configFilePresenceInfo.exists = false; + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.NoAction); + } + else { + // Either the config file was created or changed + // Reload the projects + configFilePresenceInfo.exists = true; + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles); + } } private removeProject(project: Project) { @@ -882,13 +865,131 @@ namespace ts.server { private configFileExists(configFileName: NormalizedPath) { const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); - const cachedResult = this.mapOfTsConfigPresence.get(canonicalConfigFilePath); - if (cachedResult) { - // Use the information here to answer the question + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (configFilePresenceInfo) { + return configFilePresenceInfo.exists; } return this.host.fileExists(configFileName); } + private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath); + if (configFilePresenceInfo) { + configFilePresenceInfo.exists = true; + // close existing watcher + if (configFilePresenceInfo.configFileWatcher) { + const configFileName = project.getConfigFilePath(); + this.closeFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated + ); + configFilePresenceInfo.configFileWatcher = undefined; + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + } + else { + // Mark existence of the config file with the project creation + this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { exists: true }); + } + } + + /* @internal */ + setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath); + Debug.assert(!!configFilePresenceInfo); + if (configFilePresenceInfo.trackingOpenFiles) { + const configFileName = closedProject.getConfigFilePath(); + configFilePresenceInfo.configFileWatcher = this.addFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + (_filename, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind) + ); + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + else { + // There is no one tracking anymore. Remove the status + this.mapOfConfigFilePresence.delete(closedProject.canonicalConfigFilePath); + } + } + + private logTrackingFiles(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { + const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath; + const files = map(configFilePresenceInfo.trackingOpenFiles, info => info.fileName); + this.logger.info(`FileWatcher:: ${watchType}: File: ${configFileName} Currently Tracking for files: ${files} Status: ${status}`); + } + + private watchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) { + let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (configFilePresenceInfo) { + // Existing information - just add to tracking files + (configFilePresenceInfo.trackingOpenFiles || (configFilePresenceInfo.trackingOpenFiles = [])).push(root); + } + else { + // Add new callback + configFilePresenceInfo = { + exists: this.host.fileExists(configFileName), + trackingOpenFiles: [root], + configFileWatcher: this.addFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + (_fileName, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind) + ) + }; + this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); + } + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + } + + private closeWatchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo, reason: WatcherCloseReason) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + Debug.assert(!!configFilePresenceInfo); + if (configFilePresenceInfo.trackingOpenFiles.length === 1) { + configFilePresenceInfo.trackingOpenFiles = undefined; + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + if (configFilePresenceInfo.configFileWatcher) { + this.closeFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, + configFileName, configFilePresenceInfo.configFileWatcher, reason + ); + this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); + } + } + else { + removeItemFromSet(configFilePresenceInfo.trackingOpenFiles, root); + this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + } + } + + private enumerateWatchingRootOfInferredProject(root: ScriptInfo, + action: (configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) => void) { + let current = root.fileName; + let currentPath = getDirectoryPath(root.path); + let parentPath = getDirectoryPath(currentPath); + while (currentPath !== parentPath) { + current = asNormalizedPath(getDirectoryPath(current)); + action(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), root); + //if (root.isJavaScript()) { + // this.watchConfigFileForInferredRoot(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), root); + //} + currentPath = parentPath; + parentPath = getDirectoryPath(parentPath); + } + } + + /*@internal*/ + startWatchingRootOfInferredProject(root: ScriptInfo) { + this.enumerateWatchingRootOfInferredProject(root, + (configFileName, canonicalConfigFilePath, root) => + this.watchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root) + ); + } + + /*@internal*/ + stopWatchingRootOfInferredProject(root: ScriptInfo, reason: WatcherCloseReason) { + this.enumerateWatchingRootOfInferredProject(root, + (configFileName, canonicalConfigFilePath, root) => + this.closeWatchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root, reason) + ); + } + /** * This function tries to search for a tsconfig.json for the given file. * This is different from the method the compiler uses because @@ -1124,6 +1225,7 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); this.configuredProjects.push(project); + this.setConfigFilePresenceByNewConfiguredProject(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; } @@ -1172,7 +1274,7 @@ namespace ts.server { if (scriptInfo.isScriptOpen()) { // if file is already root in some inferred project // - remove the file from that project and delete the project if necessary - this.removRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo); + this.removeRootOfInferredProjectIfNowPartOfOtherProject(scriptInfo); } } } @@ -1261,11 +1363,7 @@ namespace ts.server { : new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects); project.addRoot(root); - - this.directoryWatchers.startWatchingContainingDirectoriesForFile( - root.fileName, - project, - fileName => this.onConfigFileAddedForInferredProject(fileName)); + this.startWatchingRootOfInferredProject(root); project.updateGraph(); if (!useExistingProject) { @@ -1415,11 +1513,22 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); - this.reloadConfiguredProjectForFiles(this.openFiles, project => this.reloadConfiguredProject(project)); + this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false); this.refreshInferredProjects(); } - reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], reload: (project: ConfiguredProject) => void) { + delayReloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) { + this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); + this.delayInferredProjectsRefresh(); + } + + /** + * This function goes through all the openFiles and tries to file the config file for them. + * If the config file is found and it refers to existing project, it reloads it either immediately + * or schedules it for reload depending on delayedReload option + * If the there is no existing project it just opens the configured project for the config file + */ + reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) { const mapUpdatedProjects = createMap(); // try to reload config file for all open files for (const info of openFiles) { @@ -1435,7 +1544,13 @@ namespace ts.server { mapUpdatedProjects.set(configFileName, true); } else if (!mapUpdatedProjects.has(configFileName)) { - reload(project); + if (delayReload) { + project.pendingReload = true; + this.delayUpdateProjectGraph(project); + } + else { + this.reloadConfiguredProject(project); + } mapUpdatedProjects.set(configFileName, true); } } @@ -1448,7 +1563,7 @@ namespace ts.server { * - references in inferred project supercede the root part * - root/reference in non-inferred project beats root in inferred project */ - private removRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { + private removeRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { if (info.containingProjects.length > 1 && info.containingProjects[0].projectKind === ProjectKind.Inferred && info.containingProjects[0].isRoot(info)) { @@ -1478,7 +1593,7 @@ namespace ts.server { } // Or remove the root of inferred project if is referenced in more than one projects else { - this.removRootOfInferredProjectIfNowPartOfOtherProject(info); + this.removeRootOfInferredProjectIfNowPartOfOtherProject(info); } } @@ -1766,7 +1881,8 @@ namespace ts.server { const rootFiles: protocol.ExternalFile[] = []; for (const file of proj.rootFiles) { const normalized = toNormalizedPath(file.fileName); - if (getBaseFileName(normalized) === "tsconfig.json") { + const baseFileName = getBaseFileName(normalized); + if (baseFileName === "tsconfig.json" || baseFileName === "jsconfig.json") { if (this.host.fileExists(normalized)) { (tsConfigFiles || (tsConfigFiles = [])).push(normalized); } diff --git a/src/server/project.ts b/src/server/project.ts index 1b99e76545e..2e14c75efc5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -648,7 +648,7 @@ namespace ts.server { missingFilePath => { const fileWatcher = this.projectService.addFileWatcher( WatchType.MissingFilePath, this, missingFilePath, - (filename: string, eventKind: FileWatcherEventKind) => { + (filename, eventKind) => { if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { this.missingFilesMap.delete(missingFilePath); this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); @@ -911,9 +911,6 @@ namespace ts.server { super.setCompilerOptions(newOptions); } - // Used to keep track of what directories are watched for this project - directoriesWatchedForTsconfig: string[] = []; - constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) { super(InferredProject.newName(), ProjectKind.Inferred, @@ -927,6 +924,7 @@ namespace ts.server { } addRoot(info: ScriptInfo) { + this.projectService.startWatchingRootOfInferredProject(info); if (!this._isJsInferredProject && info.isJavaScript()) { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } @@ -934,6 +932,7 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { + this.projectService.stopWatchingRootOfInferredProject(info, WatcherCloseReason.NotNeeded); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { @@ -958,11 +957,8 @@ namespace ts.server { } close() { + forEach(this.getRootScriptInfos(), root => this.projectService.stopWatchingRootOfInferredProject(root, WatcherCloseReason.ProjectClose)); super.close(); - - for (const directory of this.directoriesWatchedForTsconfig) { - this.projectService.stopWatchingDirectory(directory); - } } getTypeAcquisition(): TypeAcquisition { @@ -1212,6 +1208,7 @@ namespace ts.server { if (this.configFileWatcher) { this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); this.configFileWatcher = undefined; + this.projectService.setConfigFilePresenceByClosedConfigFile(this); } this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); From 62663a10ba4c2f902d22d2f8a82f2d2df3078510 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 15 Jul 2017 20:03:55 -0700 Subject: [PATCH 025/144] Use map for configured project instead of the array --- src/harness/unittests/projectErrors.ts | 10 +- .../unittests/tsserverProjectSystem.ts | 113 ++++++++++-------- src/harness/unittests/typingsInstaller.ts | 10 +- src/server/editorServices.ts | 16 +-- src/server/session.ts | 2 +- 5 files changed, 78 insertions(+), 73 deletions(-) diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 3d093300cbc..0143aee7701 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -98,7 +98,7 @@ namespace ts.projectSystem { const projectService = session.getProjectService(); openFilesForSession([file1], session); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); const compilerOptionsRequest = { type: "request", command: server.CommandNames.CompilerOptionsDiagnosticsFull, @@ -141,7 +141,7 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); - const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, [ "'{' expected." ]); @@ -155,7 +155,7 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); - const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, []); } }); @@ -186,7 +186,7 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); - const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, []); } // break config and trigger watcher @@ -196,7 +196,7 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); - const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + const projectErrors = configuredProjectAt(projectService, 0).getAllProjectErrors(); checkProjectErrorsWorker(projectErrors, [ "'{' expected." ]); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 22bf06bef65..e26df827314 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -295,7 +295,7 @@ namespace ts.projectSystem { } export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { - assert.equal(projectService.configuredProjects.length, expected, `expected ${expected} configured project(s)`); + assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); } export function checkNumberOfExternalProjects(projectService: server.ProjectService, expected: number) { @@ -312,6 +312,15 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, count.inferredProjects || 0); } + export function configuredProjectAt(projectService: server.ProjectService, index: number) { + const values = projectService.configuredProjects.values(); + while (index > 0) { + values.next(); + index--; + } + return values.next().value; + } + export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) { checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); } @@ -824,12 +833,12 @@ namespace ts.projectSystem { options: {} }); service.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], [upperCaseConfigFilePath]); + checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); service.openClientFile(f1.path); service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], [upperCaseConfigFilePath]); + checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); checkProjectActualFiles(service.inferredProjects[0], [f1.path]); }); @@ -866,7 +875,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 0); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open @@ -922,7 +931,7 @@ namespace ts.projectSystem { checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectRootFiles(project, [commonFile1.path]); // add a new ts file @@ -949,7 +958,7 @@ namespace ts.projectSystem { projectService.openClientFile(commonFile2.path); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectRootFiles(project, [commonFile1.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -1024,7 +1033,7 @@ namespace ts.projectSystem { projectService.openClientFile(commonFile1.path); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); // delete commonFile2 @@ -1085,7 +1094,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(commonFile1.path); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); configFile.content = `{ "compilerOptions": {}, @@ -1121,7 +1130,7 @@ namespace ts.projectSystem { projectService.openClientFile(commonFile1.path); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); projectService.openClientFile(excludedFile1.path); checkNumberOfInferredProjects(projectService, 1); @@ -1157,7 +1166,7 @@ namespace ts.projectSystem { projectService.openClientFile(classicModuleFile.path); checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); @@ -1241,7 +1250,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(commonFile1.path); checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(projectService.configuredProjects[0], [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); }); it("should disable features when the files are too large", () => { @@ -1730,7 +1739,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, file3, configFile]); host.checkTimeoutQueueLengthAndRun(1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); }); it("correctly migrate files between projects", () => { @@ -1788,14 +1797,14 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, configFile.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); host.reloadFS([file1, file2, configFile]); host.checkTimeoutQueueLengthAndRun(2); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); }); it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { @@ -1817,7 +1826,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, configFile.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); const modifiedConfigFile = { path: configFile.path, @@ -1828,7 +1837,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); }); it("can update configured project when set of root files was not changed", () => { @@ -1850,7 +1859,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, configFile.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); const modifiedConfigFile = { path: configFile.path, @@ -1860,7 +1869,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, modifiedConfigFile]); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); }); it("can correctly update external project when set of root files has changed", () => { @@ -1930,11 +1939,11 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); host.reloadFS([file1, file2]); host.checkTimeoutQueueLengthAndRun(1); @@ -1968,13 +1977,13 @@ namespace ts.projectSystem { }); projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); projectService.closeClientFile(f1.path); projectService.openClientFile(f2.path); projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); }); @@ -1998,7 +2007,7 @@ namespace ts.projectSystem { // HTML file will not be included in any projects yet checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const configuredProj = projectService.configuredProjects[0]; + const configuredProj = configuredProjectAt(projectService, 0); checkProjectActualFiles(configuredProj, [file1.path, config.path]); // Specify .html extension as mixed content @@ -2008,8 +2017,8 @@ namespace ts.projectSystem { // The configured project should now be updated to include html file checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects[0], configuredProj, "Same configured project should be updated"); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); + assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Open HTML file projectService.applyChangesInOpenFiles( @@ -2019,10 +2028,10 @@ namespace ts.projectSystem { // Now HTML file is included in the project checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Check identifiers defined in HTML content are available in .ts file - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1); assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`); @@ -2034,7 +2043,7 @@ namespace ts.projectSystem { // HTML file is still included in project checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); // Check identifiers defined in HTML content are not available in .ts file completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5); @@ -2070,7 +2079,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); - let diagnostics = projectService.configuredProjects[0].getLanguageService().getCompilerOptionsDiagnostics(); + let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); assert.deepEqual(diagnostics, []); // #2. Ensure no errors when allowJs is false @@ -2089,7 +2098,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = projectService.configuredProjects[0].getLanguageService().getCompilerOptionsDiagnostics(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); assert.deepEqual(diagnostics, []); // #3. Ensure no errors when compiler options aren't specified @@ -2108,7 +2117,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = projectService.configuredProjects[0].getLanguageService().getCompilerOptionsDiagnostics(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); assert.deepEqual(diagnostics, []); // #4. Ensure no errors when files are explicitly specified in tsconfig @@ -2127,7 +2136,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = projectService.configuredProjects[0].getLanguageService().getCompilerOptionsDiagnostics(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); assert.deepEqual(diagnostics, []); // #4. Ensure no errors when files are explicitly excluded in tsconfig @@ -2146,7 +2155,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = projectService.configuredProjects[0].getLanguageService().getCompilerOptionsDiagnostics(); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); assert.deepEqual(diagnostics, []); }); @@ -2273,7 +2282,7 @@ namespace ts.projectSystem { projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project1 = projectService.configuredProjects[0]; + const project1 = configuredProjectAt(projectService, 0); assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 1"); assert.equal(project1.getScriptInfo(file2.path).containingProjects.length, 1, "containing projects count"); @@ -2281,7 +2290,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 2 }); assert.equal(project1.openRefCount, 2, "Open ref count in project1 - 2"); - const project2 = projectService.configuredProjects[1]; + const project2 = configuredProjectAt(projectService, 1); assert.equal(project2.openRefCount, 1, "Open ref count in project2 - 2"); assert.equal(project1.getScriptInfo(file1.path).containingProjects.length, 2, `${file1.path} containing projects count`); @@ -2413,7 +2422,7 @@ namespace ts.projectSystem { }); const projectService = session.getProjectService(); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); assert.isFalse(project.languageServiceEnabled, "Language service enabled"); assert.isTrue(!!lastEvent, "should receive event"); assert.equal(lastEvent.data.project, project, "project name"); @@ -2462,7 +2471,7 @@ namespace ts.projectSystem { const projectService = session.getProjectService(); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects[0]; + const project = configuredProjectAt(projectService, 0); assert.isFalse(project.languageServiceEnabled, "Language service enabled"); assert.isTrue(!!lastEvent, "should receive event"); assert.equal(lastEvent.data.project, project, "project name"); @@ -2650,7 +2659,7 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, tsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); // rename tsconfig.json back to lib.ts host.reloadFS([f1, f2]); @@ -2707,8 +2716,8 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path, cTsconfig.path]); - checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path, dTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); // remove one config file projectService.openExternalProject({ @@ -2718,7 +2727,7 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [dLib.path, dTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]); // remove second config file projectService.openExternalProject({ @@ -2738,8 +2747,8 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path, cTsconfig.path]); - checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path, dTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]); // close all projects - no projects should be opened projectService.closeExternalProject(projectName); @@ -2795,13 +2804,13 @@ namespace ts.projectSystem { projectService.openClientFile(app.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, app.path, config1.path]); host.reloadFS([libES5, libES2015Promise, app, config2]); host.checkTimeoutQueueLengthAndRun(2); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [libES5.path, libES2015Promise.path, app.path, config2.path]); }); it("should handle non-existing directories in config file", () => { @@ -2856,7 +2865,7 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, barTypings.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, barTypings.path, config.path]); }); }); @@ -2927,7 +2936,7 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t1.path, tsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t1.path, tsconfig.path]); // delete t1 host.reloadFS([f1, tsconfig]); @@ -2935,7 +2944,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, tsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); // create t2 host.reloadFS([f1, tsconfig, t2]); @@ -2943,7 +2952,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t2.path, tsconfig.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t2.path, tsconfig.path]); }); }); @@ -3120,7 +3129,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, node.path, config.path]); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); }); }); @@ -3496,7 +3505,7 @@ namespace ts.projectSystem { checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 1); - const configuredProject = projectService.configuredProjects[0]; + const configuredProject = configuredProjectAt(projectService, 0); checkProjectActualFiles(configuredProject, [configFile.path]); const inferredProject = projectService.inferredProjects[0]; @@ -4157,7 +4166,7 @@ namespace ts.projectSystem { const projectService = session.getProjectService(); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const projectName = projectService.configuredProjects[0].getProjectName(); + const projectName = configuredProjectAt(projectService, 0).getProjectName(); const diags = session.executeCommand({ type: "request", diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 287b0fe483d..c9617625cc7 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -92,7 +92,7 @@ namespace ts.projectSystem { const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(f1.path); service.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], [f1.path, f2.path, config.path]); + checkProjectActualFiles(configuredProjectAt(service, 0), [f1.path, f2.path, config.path]); installer.installAll(0); }); }); @@ -144,7 +144,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const p = projectService.configuredProjects[0]; + const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [file1.path, tsconfig.path]); installer.installAll(/*expectedCount*/ 1); @@ -706,7 +706,7 @@ namespace ts.projectSystem { projectService.openClientFile(app.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const p = projectService.configuredProjects[0]; + const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); installer.installAll(/*expectedCount*/ 1); @@ -753,7 +753,7 @@ namespace ts.projectSystem { projectService.openClientFile(app.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const p = projectService.configuredProjects[0]; + const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]); @@ -801,7 +801,7 @@ namespace ts.projectSystem { projectService.openClientFile(app.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const p = projectService.configuredProjects[0]; + const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); installer.installAll(/*expectedCount*/ 1); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 698dbe3ffd8..c2a79913928 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -325,7 +325,7 @@ namespace ts.server { /** * projects specified by a tsconfig.json file */ - readonly configuredProjects: ConfiguredProject[] = []; + readonly configuredProjects = createMap(); /** * list of open files */ @@ -730,7 +730,7 @@ namespace ts.server { break; case ProjectKind.Configured: // Update the map of mapOfKnownTsConfigFiles - removeItemFromSet(this.configuredProjects, project); + this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); break; case ProjectKind.Inferred: @@ -1035,7 +1035,7 @@ namespace ts.server { let counter = 0; counter = printProjects(this.logger, this.externalProjects, counter); - counter = printProjects(this.logger, this.configuredProjects, counter); + counter = printProjects(this.logger, arrayFrom(this.configuredProjects.values()), counter); counter = printProjects(this.logger, this.inferredProjects, counter); this.logger.info("Open files: "); @@ -1060,11 +1060,7 @@ namespace ts.server { private findConfiguredProjectByProjectName(configFileName: NormalizedPath) { // make sure that casing of config file name is consistent const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName)); - for (const proj of this.configuredProjects) { - if (proj.canonicalConfigFilePath === canonicalConfigFilePath) { - return proj; - } - } + return this.configuredProjects.get(canonicalConfigFilePath); } private findExternalProjectByProjectName(projectFileName: string) { @@ -1224,7 +1220,7 @@ namespace ts.server { } this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); - this.configuredProjects.push(project); + this.configuredProjects.set(project.canonicalConfigFilePath, project); this.setConfigFilePresenceByNewConfiguredProject(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; @@ -1678,7 +1674,7 @@ namespace ts.server { synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): ProjectFilesWithTSDiagnostics[] { const files: ProjectFilesWithTSDiagnostics[] = []; this.collectChanges(knownProjects, this.externalProjects, files); - this.collectChanges(knownProjects, this.configuredProjects, files); + this.collectChanges(knownProjects, arrayFrom(this.configuredProjects.values()), files); this.collectChanges(knownProjects, this.inferredProjects, files); return files; } diff --git a/src/server/session.ts b/src/server/session.ts index 7f1f571c629..02784145d4c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -498,7 +498,7 @@ namespace ts.server { private cleanup() { this.cleanProjects("inferred projects", this.projectService.inferredProjects); - this.cleanProjects("configured projects", this.projectService.configuredProjects); + this.cleanProjects("configured projects", arrayFrom(this.projectService.configuredProjects.values())); this.cleanProjects("external projects", this.projectService.externalProjects); if (this.host.gc) { this.logger.info(`host.gc()`); From dcbd7b10f19037dc907774a8fc0877e316a6cd3c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 15 Jul 2017 22:26:10 -0700 Subject: [PATCH 026/144] Combine the logic to find config file as well as the watch. This should speed up the file open scenarios where the file belongs to same configured project as we would use cache to answer those fileExists answers --- .../unittests/tsserverProjectSystem.ts | 16 +- src/harness/unittests/typingsInstaller.ts | 2 +- src/server/editorServices.ts | 216 ++++++++---------- src/server/project.ts | 4 - 4 files changed, 109 insertions(+), 129 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index e26df827314..e662e7b792c 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -809,7 +809,9 @@ namespace ts.projectSystem { const project = projectService.inferredProjects[0]; checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - checkWatchedFiles(host, ["/a/b/c/tsconfig.json", "/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path, moduleFile.path]); + const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; + const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); }); it("can handle tsconfig file name with difference casing", () => { @@ -879,7 +881,8 @@ namespace ts.projectSystem { checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + // And also tsconfig files for the open files + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path, "/a/b/c/tsconfig.json", "/a/b/c/jsconfig.json"]); checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); }); @@ -899,15 +902,16 @@ namespace ts.projectSystem { projectService.openClientFile(commonFile2.path); checkNumberOfInferredProjects(projectService, 2); - checkWatchedFiles(host, [configFile.path, "/a/tsconfig.json", libFile.path]); + const configFileLocations = ["/", "/a/", "/a/b/"]; + const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(libFile.path); + checkWatchedFiles(host, watchedFiles); // Add a tsconfig file host.reloadFS(filesWithConfig); host.checkTimeoutQueueLengthAndRun(1); checkNumberOfInferredProjects(projectService, 1); checkNumberOfConfiguredProjects(projectService, 1); - // watching all files except one that was open - checkWatchedFiles(host, [libFile.path, configFile.path, "/a/tsconfig.json"]); + checkWatchedFiles(host, watchedFiles); // remove the tsconfig file host.reloadFS(filesWithoutConfig); @@ -917,7 +921,7 @@ namespace ts.projectSystem { checkNumberOfInferredProjects(projectService, 2); checkNumberOfConfiguredProjects(projectService, 0); - checkWatchedFiles(host, ["/a/b/tsconfig.json", "/a/tsconfig.json", libFile.path]); + checkWatchedFiles(host, watchedFiles); }); it("add new files to a configured project without file list", () => { diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index c9617625cc7..7fab4ffdcc4 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -755,7 +755,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); - checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]); + checkWatchedFiles(host, ["/tsconfig.json", jsconfig.path, "/bower_components", "/node_modules", libFile.path]); installer.installAll(/*expectedCount*/ 1); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index c2a79913928..c2b712c06a2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -252,7 +252,7 @@ namespace ts.server { WildCardDirectories = "Wild card directory", TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", - ConfigFileForInferredRoot = "Config file for the root script info of the inferred project" + ConfigFileForOpenFile = "Config file changes for the open script info" } /* @internal */ @@ -266,12 +266,12 @@ namespace ts.server { OrphanScriptInfo = "Removing Orphan script info as part of cleanup", FileDeleted = "File was deleted", FileOpened = "File opened", - ConfigProjectCreated = "Config file project created" + ConfigProjectCreated = "Config file project created", + FileClosed = "File is closed" } const enum ConfigFileWatcherStatus { ReloadingFiles = "Reloading configured projects files", - NoAction = "No action on files", UpdatedCallback = "Updated the callback", TrackingFileAdded = "Tracking file added", TrackingFileRemoved = "Tracking file removed" @@ -282,7 +282,7 @@ namespace ts.server { type ConfigFileExistence = { exists: boolean; - trackingOpenFiles?: ScriptInfo[]; + trackingOpenFileSet?: Map; configFileWatcher?: FileWatcher; }; @@ -672,48 +672,31 @@ namespace ts.server { // Update the cached status // No action needed on tracking open files since the existing config file anyways didnt affect the tracking file configFilePresenceInfo.exists = false; - this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.NoAction); 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 - const orphanFiles = filter(this.openFiles, file => file.containingProjects.length === 0); - this.delayReloadConfiguredProjectForFiles(orphanFiles); + // Reload the configured projects for the open files in the map as they are affectected by this config file + this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); } else { + this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); project.pendingReload = true; - this.logTrackingFiles(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - if (configFilePresenceInfo.trackingOpenFiles) { - this.delayUpdateProjectGraph(project); - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles); - } - else { - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); - } + this.delayUpdateProjectGraph(project); + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); } } /** - * This is the callback function for the config file add/remove/change for the root in the inferred project + * This is the callback function for the config file add/remove/change at any location that matters to open + * script info but doesnt have configured project open for the config file */ - private onConfigFileAddedForInferredProject(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) { + private onConfigFileChangeForOpenScriptInfo(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) { // This callback is called only if we dont have config file project for this config file const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath); - - if (eventKind === FileWatcherEventKind.Deleted) { - // No action needed if the event was for deletion of the file - // - because the existing config file didnt affect the inferred project roots anyways - configFilePresenceInfo.exists = false; - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.NoAction); - } - else { - // Either the config file was created or changed - // Reload the projects - configFilePresenceInfo.exists = true; - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFiles); - } + configFilePresenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); } private removeProject(project: Project) { @@ -729,7 +712,7 @@ namespace ts.server { this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); break; case ProjectKind.Configured: - // Update the map of mapOfKnownTsConfigFiles + this.setConfigFilePresenceByClosedConfigFile(project); this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); break; @@ -791,6 +774,7 @@ namespace ts.server { // because the user may chose to discard the buffer content before saving // to the disk, and the server's version of the file can be out of sync. info.close(); + this.stopWatchingConfigFileForScriptInfo(info); removeItemFromSet(this.openFiles, info); @@ -863,28 +847,24 @@ namespace ts.server { }); } - private configFileExists(configFileName: NormalizedPath) { + private configFileExists(configFileName: NormalizedPath, info: ScriptInfo) { const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (configFilePresenceInfo) { - return configFilePresenceInfo.exists; - } - return this.host.fileExists(configFileName); + return this.watchConfigFileForScriptInfo(configFileName, canonicalConfigFilePath, info).exists; } private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) { const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath); if (configFilePresenceInfo) { - configFilePresenceInfo.exists = true; + Debug.assert(configFilePresenceInfo.exists); // close existing watcher if (configFilePresenceInfo.configFileWatcher) { const configFileName = project.getConfigFilePath(); this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated ); configFilePresenceInfo.configFileWatcher = undefined; - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } } else { @@ -893,17 +873,16 @@ namespace ts.server { } } - /* @internal */ - setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) { + private setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) { const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath); Debug.assert(!!configFilePresenceInfo); - if (configFilePresenceInfo.trackingOpenFiles) { + if (configFilePresenceInfo.trackingOpenFileSet) { const configFileName = closedProject.getConfigFilePath(); configFilePresenceInfo.configFileWatcher = this.addFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_filename, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind) + WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, + (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) ); - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } else { // There is no one tracking anymore. Remove the status @@ -911,85 +890,83 @@ namespace ts.server { } } - private logTrackingFiles(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { - const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath; - const files = map(configFilePresenceInfo.trackingOpenFiles, info => info.fileName); + private logConfigFileWatch(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { + const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForOpenFile : WatchType.ConfigFilePath; + const files = configFilePresenceInfo.trackingOpenFileSet ? + arrayFrom(configFilePresenceInfo.trackingOpenFileSet.keys(), key => + this.getScriptInfoForPath(key as Path).fileName) : + []; this.logger.info(`FileWatcher:: ${watchType}: File: ${configFileName} Currently Tracking for files: ${files} Status: ${status}`); } - private watchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) { + private watchConfigFileForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); if (configFilePresenceInfo) { // Existing information - just add to tracking files - (configFilePresenceInfo.trackingOpenFiles || (configFilePresenceInfo.trackingOpenFiles = [])).push(root); - } - else { - // Add new callback - configFilePresenceInfo = { - exists: this.host.fileExists(configFileName), - trackingOpenFiles: [root], - configFileWatcher: this.addFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_fileName, eventKind) => this.onConfigFileAddedForInferredProject(configFileName, eventKind) - ) - }; - this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); - } - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); - } - - private closeWatchConfigFileForInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo, reason: WatcherCloseReason) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - Debug.assert(!!configFilePresenceInfo); - if (configFilePresenceInfo.trackingOpenFiles.length === 1) { - configFilePresenceInfo.trackingOpenFiles = undefined; - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); - if (configFilePresenceInfo.configFileWatcher) { - this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, - configFileName, configFilePresenceInfo.configFileWatcher, reason - ); - this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); + if (!configFilePresenceInfo.trackingOpenFileSet) { + configFilePresenceInfo.trackingOpenFileSet = createMap(); + configFilePresenceInfo.trackingOpenFileSet.set(info.path, true); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + } + else if (!configFilePresenceInfo.trackingOpenFileSet.has(info.path)) { + configFilePresenceInfo.trackingOpenFileSet.set(info.path, true); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); } } else { - removeItemFromSet(configFilePresenceInfo.trackingOpenFiles, root); - this.logTrackingFiles(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + // Add new callback + const trackingOpenFileSet = createMap(); + trackingOpenFileSet.set(info.path, true); + const exists = this.host.fileExists(configFileName); + configFilePresenceInfo = { + exists, + trackingOpenFileSet, + configFileWatcher: this.addFileWatcher( + WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, + (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) + ) + }; + this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + } + return configFilePresenceInfo; + } + + private closeConfigFileWatchForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (configFilePresenceInfo) { + if (configFilePresenceInfo.trackingOpenFileSet.size === 1) { + configFilePresenceInfo.trackingOpenFileSet = undefined; + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + if (configFilePresenceInfo.configFileWatcher) { + this.closeFileWatcher( + WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, + configFilePresenceInfo.configFileWatcher, WatcherCloseReason.FileClosed + ); + this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); + } + } + else { + configFilePresenceInfo.trackingOpenFileSet.delete(info.path); + this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + } } } - private enumerateWatchingRootOfInferredProject(root: ScriptInfo, - action: (configFileName: NormalizedPath, canonicalConfigFilePath: string, root: ScriptInfo) => void) { - let current = root.fileName; - let currentPath = getDirectoryPath(root.path); + private stopWatchingConfigFileForScriptInfo(info: ScriptInfo) { + Debug.assert(!info.isScriptOpen()); + let current = info.fileName; + let currentPath = getDirectoryPath(info.path); let parentPath = getDirectoryPath(currentPath); while (currentPath !== parentPath) { current = asNormalizedPath(getDirectoryPath(current)); - action(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), root); - //if (root.isJavaScript()) { - // this.watchConfigFileForInferredRoot(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), root); - //} + this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), info); + this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), info); currentPath = parentPath; parentPath = getDirectoryPath(parentPath); } } - /*@internal*/ - startWatchingRootOfInferredProject(root: ScriptInfo) { - this.enumerateWatchingRootOfInferredProject(root, - (configFileName, canonicalConfigFilePath, root) => - this.watchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root) - ); - } - - /*@internal*/ - stopWatchingRootOfInferredProject(root: ScriptInfo, reason: WatcherCloseReason) { - this.enumerateWatchingRootOfInferredProject(root, - (configFileName, canonicalConfigFilePath, root) => - this.closeWatchConfigFileForInferredRoot(configFileName, canonicalConfigFilePath, root, reason) - ); - } - /** * This function tries to search for a tsconfig.json for the given file. * This is different from the method the compiler uses because @@ -998,20 +975,21 @@ namespace ts.server { * The server must start searching from the directory containing * the newly opened file. */ - private getConfigFileNameForFile(fileName: NormalizedPath, projectRootPath?: NormalizedPath) { - let searchPath = getDirectoryPath(fileName); + private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) { + Debug.assert(info.isScriptOpen()); + let searchPath = getDirectoryPath(info.fileName); this.logger.info(`Search path: ${searchPath}`); // check if this file is already included in one of external projects while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) { const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); - if (this.configFileExists(tsconfigFileName)) { + if (this.configFileExists(tsconfigFileName, info)) { this.logger.info(`Config file name: ${tsconfigFileName}`); return tsconfigFileName; } const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); - if (this.configFileExists(jsconfigFileName)) { + if (this.configFileExists(jsconfigFileName, info)) { this.logger.info(`Config file name: ${jsconfigFileName}`); return jsconfigFileName; } @@ -1359,7 +1337,6 @@ namespace ts.server { : new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects); project.addRoot(root); - this.startWatchingRootOfInferredProject(root); project.updateGraph(); if (!useExistingProject) { @@ -1513,8 +1490,11 @@ namespace ts.server { this.refreshInferredProjects(); } - delayReloadConfiguredProjectForFiles(openFiles: ScriptInfo[]) { - this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); + delayReloadConfiguredProjectForFiles(openFileSet: Map) { + if (openFileSet) { + const openFiles = arrayFrom(openFileSet.keys(), path => this.getScriptInfoForPath(path as Path)); + this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); + } this.delayInferredProjectsRefresh(); } @@ -1532,7 +1512,7 @@ namespace ts.server { // 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 only if we havent already done so // otherwise we create a new one. - const configFileName = this.getConfigFileNameForFile(info.fileName); + const configFileName = this.getConfigFileNameForFile(info); if (configFileName) { let project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { @@ -1614,9 +1594,10 @@ namespace ts.server { let configFileName: NormalizedPath; let configFileErrors: Diagnostic[]; + const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); if (!project) { - configFileName = this.getConfigFileNameForFile(fileName, projectRootPath); + configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { @@ -1640,7 +1621,6 @@ namespace ts.server { } // at this point if file is the part of some configured/external project then this project should be created - const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true); // Delete the orphan files here because there might be orphan script infos (which are not part of project) // when some file/s were closed which resulted in project removal. diff --git a/src/server/project.ts b/src/server/project.ts index 2e14c75efc5..774865b335e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -924,7 +924,6 @@ namespace ts.server { } addRoot(info: ScriptInfo) { - this.projectService.startWatchingRootOfInferredProject(info); if (!this._isJsInferredProject && info.isJavaScript()) { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } @@ -932,7 +931,6 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { - this.projectService.stopWatchingRootOfInferredProject(info, WatcherCloseReason.NotNeeded); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { @@ -957,7 +955,6 @@ namespace ts.server { } close() { - forEach(this.getRootScriptInfos(), root => this.projectService.stopWatchingRootOfInferredProject(root, WatcherCloseReason.ProjectClose)); super.close(); } @@ -1208,7 +1205,6 @@ namespace ts.server { if (this.configFileWatcher) { this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); this.configFileWatcher = undefined; - this.projectService.setConfigFilePresenceByClosedConfigFile(this); } this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); From 62ef6b1cda057823d935147493a6fc48ac0f8f76 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 17 Jul 2017 12:37:36 -0700 Subject: [PATCH 027/144] Added another todo as now we are watching too many files --- src/server/editorServices.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index c2b712c06a2..8c9731ef6ea 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -849,6 +849,13 @@ namespace ts.server { private configFileExists(configFileName: NormalizedPath, info: ScriptInfo) { const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); + // TODO: (sheetalkamat) Need to reduce the number of watches by not watching directories right here. + // Theorotically current approach is correct but in practice there will be very few scenarios where + // the config file gets added somewhere inside the another config file. And technically we could handle that case in configDirectory watcher in some cases + // But given that its a rare scenario it seems like too much overhead. + // So what we want to be watching is: configFile if the project is open + // And the whole chain only for the inferred project roots + // instead create the cache and send it across, and put watches only if info gets added to the inferred project root? return this.watchConfigFileForScriptInfo(configFileName, canonicalConfigFilePath, info).exists; } From 2439e7affb368430d39b0e257b8ba2ff5fc88d60 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 17 Jul 2017 15:20:59 -0700 Subject: [PATCH 028/144] Reduce the number of watched config files by watching the chain only in case of inferred root --- src/compiler/core.ts | 11 +- .../unittests/tsserverProjectSystem.ts | 3 +- src/harness/unittests/typingsInstaller.ts | 2 +- src/server/editorServices.ts | 379 ++++++++++++------ src/server/project.ts | 3 + 5 files changed, 270 insertions(+), 128 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index a1880b46471..b4ea5943eec 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -467,13 +467,20 @@ namespace ts { return result; } - export function flatMapIter(iter: Iterator, mapfn: (x: T) => U[] | undefined): U[] { + export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[] { const result: U[] = []; while (true) { const { value, done } = iter.next(); if (done) break; const res = mapfn(value); - if (res) result.push(...res); + if (res) { + if (isArray(res)) { + result.push(...res); + } + else { + result.push(res); + } + } } return result; } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index e662e7b792c..a63a8c3daf8 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -881,8 +881,7 @@ namespace ts.projectSystem { checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open - // And also tsconfig files for the open files - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path, "/a/b/c/tsconfig.json", "/a/b/c/jsconfig.json"]); + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); }); diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 7fab4ffdcc4..c9617625cc7 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -755,7 +755,7 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); - checkWatchedFiles(host, ["/tsconfig.json", jsconfig.path, "/bower_components", "/node_modules", libFile.path]); + checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]); installer.installAll(/*expectedCount*/ 1); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8c9731ef6ea..5f660defcf2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -252,7 +252,7 @@ namespace ts.server { WildCardDirectories = "Wild card directory", TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", - ConfigFileForOpenFile = "Config file changes for the open script info" + ConfigFileForInferredRoot = "Config file for the inferred project root" } /* @internal */ @@ -271,18 +271,32 @@ namespace ts.server { } const enum ConfigFileWatcherStatus { - ReloadingFiles = "Reloading configured projects files", + ReloadingFiles = "Reloading configured projects for files", + ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files", UpdatedCallback = "Updated the callback", TrackingFileAdded = "Tracking file added", - TrackingFileRemoved = "Tracking file removed" + TrackingFileRemoved = "Tracking file removed", + InferredRootAdded = "Inferred Root file added", + InferredRootRemoved = "Inferred Root file removed", } /* @internal */ export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; type ConfigFileExistence = { + /** + * Cached value of existence of config file + */ exists: boolean; - trackingOpenFileSet?: Map; + /** + * The value in the open files map is true if the file is inferred project root + * Otherwise its false + */ + trackingOpenFilesMap: Map; + /** + * The file watcher corresponding to this config file for the inferred project root + * The watcher is present only when there is no open configured project for this config file + */ configFileWatcher?: FileWatcher; }; @@ -675,14 +689,16 @@ namespace ts.server { this.removeProject(project); // Reload the configured projects for the open files in the map as they are affectected by this config file - this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); + this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + // Since the configured project was deleted, we want to reload projects for all the open files + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false); } else { - this.logConfigFileWatch(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); project.pendingReload = true; this.delayUpdateProjectGraph(project); - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); + // As we scheduled the updated project graph, we would need to only schedule the project reload for the inferred project roots + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ true); } } @@ -695,8 +711,9 @@ namespace ts.server { const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath); configFilePresenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFileSet); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + // The tracking opens files would only contaion the inferred root so no need to check + this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false); } private removeProject(project: Project) { @@ -712,9 +729,9 @@ namespace ts.server { this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); break; case ProjectKind.Configured: - this.setConfigFilePresenceByClosedConfigFile(project); this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); + this.setConfigFilePresenceByClosedConfigFile(project); break; case ProjectKind.Inferred: removeItemFromSet(this.inferredProjects, project); @@ -774,7 +791,7 @@ namespace ts.server { // because the user may chose to discard the buffer content before saving // to the disk, and the server's version of the file can be out of sync. info.close(); - this.stopWatchingConfigFileForScriptInfo(info); + this.stopWatchingConfigFilesForClosedScriptInfo(info); removeItemFromSet(this.openFiles, info); @@ -847,16 +864,34 @@ namespace ts.server { }); } - private configFileExists(configFileName: NormalizedPath, info: ScriptInfo) { - const canonicalConfigFilePath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); - // TODO: (sheetalkamat) Need to reduce the number of watches by not watching directories right here. - // Theorotically current approach is correct but in practice there will be very few scenarios where - // the config file gets added somewhere inside the another config file. And technically we could handle that case in configDirectory watcher in some cases - // But given that its a rare scenario it seems like too much overhead. - // So what we want to be watching is: configFile if the project is open - // And the whole chain only for the inferred project roots - // instead create the cache and send it across, and put watches only if info gets added to the inferred project root? - return this.watchConfigFileForScriptInfo(configFileName, canonicalConfigFilePath, info).exists; + private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { + let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (configFilePresenceInfo) { + // By default the info is belong to the config file. + // Only adding the info as a root to inferred project will make it the root + if (!configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) { + configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + } + return configFilePresenceInfo.exists; + } + + // Theorotically we should be adding watch for the directory here itself. + // In practice there will be very few scenarios where the config file gets added + // somewhere inside the another config file directory. + // And technically we could handle that case in configFile's directory watcher in some cases + // But given that its a rare scenario it seems like too much overhead. (we werent watching those directories earlier either) + // So what we are now watching is: configFile if the project is open + // And the whole chain of config files only for the inferred project roots + + // Cache the host value of file exists and add the info tio to the tracked root + const trackingOpenFilesMap = createMap(); + trackingOpenFilesMap.set(info.path, false); + const exists = this.host.fileExists(configFileName); + configFilePresenceInfo = { exists, trackingOpenFilesMap }; + this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + return exists; } private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) { @@ -867,29 +902,41 @@ namespace ts.server { if (configFilePresenceInfo.configFileWatcher) { const configFileName = project.getConfigFilePath(); this.closeFileWatcher( - WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated ); configFilePresenceInfo.configFileWatcher = undefined; - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } } else { - // Mark existence of the config file with the project creation - this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { exists: true }); + // We could be in this scenario if it is the external project tracked configured file + // Since that route doesnt check if the config file is present or not + this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { + exists: true, + trackingOpenFilesMap: createMap() + }); } } + private configFileExistenceTracksInferredRoot(configFilePresenceInfo: ConfigFileExistence) { + return forEachEntry(configFilePresenceInfo.trackingOpenFilesMap, (value, __key) => value); + } + private setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) { const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath); Debug.assert(!!configFilePresenceInfo); - if (configFilePresenceInfo.trackingOpenFileSet) { + const trackingOpenFilesMap = configFilePresenceInfo.trackingOpenFilesMap; + if (trackingOpenFilesMap.size) { const configFileName = closedProject.getConfigFilePath(); - configFilePresenceInfo.configFileWatcher = this.addFileWatcher( - WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, - (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + if (this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) { + Debug.assert(!configFilePresenceInfo.configFileWatcher); + configFilePresenceInfo.configFileWatcher = this.addFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) + ); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } } else { // There is no one tracking anymore. Remove the status @@ -897,81 +944,172 @@ namespace ts.server { } } - private logConfigFileWatch(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { - const watchType = configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForOpenFile : WatchType.ConfigFilePath; - const files = configFilePresenceInfo.trackingOpenFileSet ? - arrayFrom(configFilePresenceInfo.trackingOpenFileSet.keys(), key => - this.getScriptInfoForPath(key as Path).fileName) : - []; - this.logger.info(`FileWatcher:: ${watchType}: File: ${configFileName} Currently Tracking for files: ${files} Status: ${status}`); + private logConfigFileWatchUpdate(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { + if (this.logger.loggingEnabled()) { + const inferredRoots: string[] = []; + const otherFiles: string[] = []; + configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => { + const info = this.getScriptInfoForPath(key); + if (value) { + inferredRoots.push(info.fileName); + } + else { + otherFiles.push(info.fileName); + } + }); + const watchType = status === ConfigFileWatcherStatus.UpdatedCallback || + status === ConfigFileWatcherStatus.ReloadingFiles || + status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ? + (configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) : + ""; + this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`); + } } - private watchConfigFileForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { - let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (configFilePresenceInfo) { - // Existing information - just add to tracking files - if (!configFilePresenceInfo.trackingOpenFileSet) { - configFilePresenceInfo.trackingOpenFileSet = createMap(); - configFilePresenceInfo.trackingOpenFileSet.set(info.path, true); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); - } - else if (!configFilePresenceInfo.trackingOpenFileSet.has(info.path)) { - configFilePresenceInfo.trackingOpenFileSet.set(info.path, true); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); - } + private closeConfigFileWatcherIfInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, + configFilePresenceInfo: ConfigFileExistence, infoIsInferredRoot: boolean, reason: WatcherCloseReason) { + // Close the config file watcher if it was the last inferred root + if (infoIsInferredRoot && + configFilePresenceInfo.configFileWatcher && + !this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) { + this.closeFileWatcher( + WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + configFilePresenceInfo.configFileWatcher, reason + ); + configFilePresenceInfo.configFileWatcher = undefined; } - else { - // Add new callback - const trackingOpenFileSet = createMap(); - trackingOpenFileSet.set(info.path, true); - const exists = this.host.fileExists(configFileName); - configFilePresenceInfo = { - exists, - trackingOpenFileSet, - configFileWatcher: this.addFileWatcher( - WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, - (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ) - }; - this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + + // If this was the last tracking file open for this config file, remove the cached value + if (!configFilePresenceInfo.trackingOpenFilesMap.size && + !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { + this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); } - return configFilePresenceInfo; } - private closeConfigFileWatchForScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { + private closeConfigFileWatchForClosedScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); if (configFilePresenceInfo) { - if (configFilePresenceInfo.trackingOpenFileSet.size === 1) { - configFilePresenceInfo.trackingOpenFileSet = undefined; - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); - if (configFilePresenceInfo.configFileWatcher) { - this.closeFileWatcher( - WatchType.ConfigFileForOpenFile, /*project*/ undefined, configFileName, - configFilePresenceInfo.configFileWatcher, WatcherCloseReason.FileClosed - ); - this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); - } - } - else { - configFilePresenceInfo.trackingOpenFileSet.delete(info.path); - this.logConfigFileWatch(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); - } + const isInferredRoot = configFilePresenceInfo.trackingOpenFilesMap.get(info.path); + + // Delete the info from tracking + configFilePresenceInfo.trackingOpenFilesMap.delete(info.path); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); + + // Close the config file watcher if it was the last inferred root + this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath, + configFilePresenceInfo, isInferredRoot, WatcherCloseReason.FileClosed + ); } } - private stopWatchingConfigFileForScriptInfo(info: ScriptInfo) { + /** + * This is called on file close, so that we stop watching the config file for this script info + * @param info + */ + private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.isScriptOpen()); - let current = info.fileName; - let currentPath = getDirectoryPath(info.path); - let parentPath = getDirectoryPath(currentPath); - while (currentPath !== parentPath) { - current = asNormalizedPath(getDirectoryPath(current)); - this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "tsconfig.json")), combinePaths(currentPath, "tsconfig.json"), info); - this.closeConfigFileWatchForScriptInfo(asNormalizedPath(combinePaths(current, "jsconfig.json")), combinePaths(currentPath, "jsconfig.json"), info); - currentPath = parentPath; - parentPath = getDirectoryPath(parentPath); + this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.closeConfigFileWatchForClosedScriptInfo(configFileName, canonicalConfigFilePath, info) + ); + } + + private watchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { + let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (!configFilePresenceInfo) { + // Create the cache + configFilePresenceInfo = { + exists: this.host.fileExists(configFileName), + trackingOpenFilesMap: createMap() + }; + this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); } + + // Set this file as inferred root + configFilePresenceInfo.trackingOpenFilesMap.set(info.path, true); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootAdded); + + // If there is no configured project for this config file, create the watcher + if (!configFilePresenceInfo.configFileWatcher && + !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { + configFilePresenceInfo.configFileWatcher = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) + ); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + } + + /** + * This is called by inferred project whenever script info is added as a root + */ + /* @internal */ + startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { + Debug.assert(info.isScriptOpen()); + this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.watchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info) + ); + } + + private closeWatchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo, reason: WatcherCloseReason) { + const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); + if (configFilePresenceInfo) { + // Set this as not inferred root + if (configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) { + configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false); + this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootRemoved); + } + + // Close the watcher if present + this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath, + configFilePresenceInfo, /*infoIsInferredRoot*/ true, reason + ); + } + } + + /** + * This is called by inferred project whenever root script info is removed from it + */ + /* @internal */ + stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { + this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.closeWatchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info, reason) + ); + } + + /** + * This function tries to search for a tsconfig.json for the given file. + * This is different from the method the compiler uses because + * 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 enumerateConfigFileLocations(info: ScriptInfo, + action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void, + projectRootPath?: NormalizedPath) { + let searchPath = asNormalizedPath(getDirectoryPath(info.fileName)); + + while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) { + const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName); + const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); + let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json")); + if (result) { + return tsconfigFileName; + } + + const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); + result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json")); + if (result) { + return jsconfigFileName; + } + + const parentPath = asNormalizedPath(getDirectoryPath(searchPath)); + if (parentPath === searchPath) { + break; + } + searchPath = parentPath; + } + + return undefined; } /** @@ -984,31 +1122,19 @@ namespace ts.server { */ private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) { Debug.assert(info.isScriptOpen()); - let searchPath = getDirectoryPath(info.fileName); - this.logger.info(`Search path: ${searchPath}`); - - // check if this file is already included in one of external projects - while (!projectRootPath || searchPath.indexOf(projectRootPath) >= 0) { - const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json")); - if (this.configFileExists(tsconfigFileName, info)) { - this.logger.info(`Config file name: ${tsconfigFileName}`); - return tsconfigFileName; - } - - const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json")); - if (this.configFileExists(jsconfigFileName, info)) { - this.logger.info(`Config file name: ${jsconfigFileName}`); - return jsconfigFileName; - } - - const parentPath = asNormalizedPath(getDirectoryPath(searchPath)); - if (parentPath === searchPath) { - break; - } - searchPath = parentPath; + this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); + const configFileName = this.enumerateConfigFileLocations(info, + (configFileName: NormalizedPath, canonicalConfigFilePath: string) => + this.configFileExists(configFileName, canonicalConfigFilePath, info), + projectRootPath + ); + if (configFileName) { + this.logger.info(`For info: ${info.fileName} :: Config file name: ${configFileName}`); } - this.logger.info("No config files found."); - return undefined; + else { + this.logger.info(`For info: ${info.fileName} :: No config files found.`); + } + return configFileName; } private printProjects() { @@ -1045,6 +1171,10 @@ namespace ts.server { private findConfiguredProjectByProjectName(configFileName: NormalizedPath) { // make sure that casing of config file name is consistent const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName)); + return this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath); + } + + private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string) { return this.configuredProjects.get(canonicalConfigFilePath); } @@ -1497,11 +1627,14 @@ namespace ts.server { this.refreshInferredProjects(); } - delayReloadConfiguredProjectForFiles(openFileSet: Map) { - if (openFileSet) { - const openFiles = arrayFrom(openFileSet.keys(), path => this.getScriptInfoForPath(path as Path)); - this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); - } + delayReloadConfiguredProjectForFiles(openFilesMap: Map, ignoreIfNotInferredProjectRoot: boolean) { + // Get open files to reload projects for + const openFiles = flatMapIter(openFilesMap.keys(), path => { + if (!ignoreIfNotInferredProjectRoot || openFilesMap.get(path)) { + return this.getScriptInfoForPath(path as Path); + } + }); + this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); this.delayInferredProjectsRefresh(); } diff --git a/src/server/project.ts b/src/server/project.ts index 774865b335e..d45f2e99be4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -924,6 +924,7 @@ namespace ts.server { } addRoot(info: ScriptInfo) { + this.projectService.startWatchingConfigFilesForInferredProjectRoot(info); if (!this._isJsInferredProject && info.isJavaScript()) { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } @@ -931,6 +932,7 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { + this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { @@ -955,6 +957,7 @@ namespace ts.server { } close() { + forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.ProjectClose)); super.close(); } From 802e283aa7252b6ae4ddcd78dc2c88c754893361 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 18 Jul 2017 17:04:29 -0700 Subject: [PATCH 029/144] Refactoring of the builder --- src/compiler/utilities.ts | 8 +- src/server/builder.ts | 500 +++++++++++++++-------------------- src/server/editorServices.ts | 5 + src/server/project.ts | 23 +- src/server/session.ts | 4 +- src/server/utilities.ts | 14 +- 6 files changed, 243 insertions(+), 311 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 76d242e8b08..088733e21ef 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1394,6 +1394,10 @@ namespace ts { return SpecialPropertyAssignmentKind.None; } + export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration { + return isModuleDeclaration(node) && node.name.kind === SyntaxKind.StringLiteral; + } + export function getExternalModuleName(node: Node): Expression { if (node.kind === SyntaxKind.ImportDeclaration) { return (node).moduleSpecifier; @@ -1407,8 +1411,8 @@ namespace ts { if (node.kind === SyntaxKind.ExportDeclaration) { return (node).moduleSpecifier; } - if (node.kind === SyntaxKind.ModuleDeclaration && (node).name.kind === SyntaxKind.StringLiteral) { - return (node).name; + if (isModuleWithStringLiteralName(node)) { + return node.name; } } diff --git a/src/server/builder.ts b/src/server/builder.ts index 8a10682b4cc..285ab9ece60 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -3,24 +3,130 @@ /// namespace ts.server { - export function shouldEmitFile(scriptInfo: ScriptInfo) { return !scriptInfo.hasMixedContent; } - /** - * An abstract file info that maintains a shape signature. - */ - export class BuilderFileInfo { + export interface Builder { + /** + * This is the callback when file infos in the builder are updated + */ + onProjectUpdateGraph(): void; + getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; + /** + * @returns {boolean} whether the emit was conducted or not + */ + emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean; + clear(): void; + } - private lastCheckedShapeSignature: string; + interface EmitHandler { + addScriptInfo(scriptInfo: ScriptInfo): void; + removeScriptInfo(path: Path): void; + updateScriptInfo(scriptInfo: ScriptInfo): void; + /** + * Gets the files affected by the script info which has updated shape from the known one + */ + getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[]; + } - constructor(public readonly scriptInfo: ScriptInfo, public readonly project: Project) { + export function createBuilder(project: Project): Builder { + let isModuleEmit: boolean | undefined; + let projectVersionForDependencyGraph: string; + // Last checked shape signature for the file info + let fileInfos: Map; + let emitHandler: EmitHandler; + return { + onProjectUpdateGraph, + getFilesAffectedBy, + emitFile, + clear + }; + + function createProjectGraph() { + const currentIsModuleEmit = project.getCompilerOptions().module !== ModuleKind.None; + if (isModuleEmit !== currentIsModuleEmit) { + isModuleEmit = currentIsModuleEmit; + emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); + fileInfos = undefined; + } + + fileInfos = mutateExistingMap( + fileInfos, arrayToMap(project.getScriptInfos(), info => info.path), + (_path, info) => { + emitHandler.addScriptInfo(info); + return ""; + }, + (path: Path, _value) => emitHandler.removeScriptInfo(path), + /*isSameValue*/ undefined, + /*OnDeleteExistingMismatchValue*/ undefined, + (_prevValue, scriptInfo) => emitHandler.updateScriptInfo(scriptInfo) + ); + projectVersionForDependencyGraph = project.getProjectVersion(); } - public isExternalModuleOrHasOnlyAmbientExternalModules() { - const sourceFile = this.getSourceFile(); - return isExternalModule(sourceFile) || this.containsOnlyAmbientModules(sourceFile); + function ensureFileInfos() { + if (!emitHandler) { + createProjectGraph(); + } + Debug.assert(projectVersionForDependencyGraph === project.getProjectVersion()); + } + + function onProjectUpdateGraph() { + if (emitHandler) { + createProjectGraph(); + } + } + + function getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { + ensureFileInfos(); + + const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; + const path = scriptInfo.path; + if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(scriptInfo)) { + return singleFileResult; + } + + return emitHandler.getFilesAffectedByUpdatedShape(scriptInfo, singleFileResult); + } + + /** + * @returns {boolean} whether the emit was conducted or not + */ + function emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean { + ensureFileInfos(); + if (!fileInfos || !fileInfos.has(scriptInfo.path)) { + return false; + } + + const { emitSkipped, outputFiles } = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ false); + if (!emitSkipped) { + const projectRootPath = project.getProjectRootPath(); + for (const outputFile of outputFiles) { + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); + writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); + } + } + return !emitSkipped; + } + + function clear() { + isModuleEmit = undefined; + emitHandler = undefined; + fileInfos = undefined; + projectVersionForDependencyGraph = undefined; + } + + function getSourceFile(path: Path) { + return project.getSourceFile(path); + } + + function getScriptInfo(path: Path) { + return project.projectService.getScriptInfoForPath(path); + } + + function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) { + return sourceFile && (isExternalModule(sourceFile) || containsOnlyAmbientModules(sourceFile)); } /** @@ -29,331 +135,149 @@ namespace ts.server { * there are no point to rebuild all script files if these special files have changed. However, if any statement * in the file is not ambient external module, we treat it as a regular script file. */ - private containsOnlyAmbientModules(sourceFile: SourceFile) { + function containsOnlyAmbientModules(sourceFile: SourceFile) { for (const statement of sourceFile.statements) { - if (statement.kind !== SyntaxKind.ModuleDeclaration || (statement).name.kind !== SyntaxKind.StringLiteral) { + if (!isModuleWithStringLiteralName(statement)) { return false; } } return true; } - private computeHash(text: string): string { - return this.project.projectService.host.createHash(text); - } - - private getSourceFile(): SourceFile { - return this.project.getSourceFile(this.scriptInfo.path); - } - /** * @return {boolean} indicates if the shape signature has changed since last update. */ - public updateShapeSignature() { - const sourceFile = this.getSourceFile(); + function updateShapeSignature(scriptInfo: ScriptInfo) { + const path = scriptInfo.path; + const sourceFile = getSourceFile(path); if (!sourceFile) { return true; } - const lastSignature = this.lastCheckedShapeSignature; + const prevSignature = fileInfos.get(path); + let latestSignature = prevSignature; if (sourceFile.isDeclarationFile) { - this.lastCheckedShapeSignature = this.computeHash(sourceFile.text); + latestSignature = computeHash(sourceFile.text); + fileInfos.set(path, latestSignature); } else { - const emitOutput = this.project.getFileEmitOutput(this.scriptInfo, /*emitOnlyDtsFiles*/ true); + const emitOutput = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ true); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - this.lastCheckedShapeSignature = this.computeHash(emitOutput.outputFiles[0].text); + latestSignature = computeHash(emitOutput.outputFiles[0].text); + fileInfos.set(path, latestSignature); } } - return !lastSignature || this.lastCheckedShapeSignature !== lastSignature; - } - } - export interface Builder { - readonly project: Project; - getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; - onProjectUpdateGraph(): void; - emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean; - clear(): void; - } - - abstract class AbstractBuilder implements Builder { - - /** - * stores set of files from the project. - * NOTE: this field is created on demand and should not be accessed directly. - * Use 'getFileInfos' instead. - */ - private fileInfos_doNotAccessDirectly: Map; - - constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) { + return !prevSignature || latestSignature !== prevSignature; } - private getFileInfos() { - return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createMap()); + function computeHash(text: string) { + return project.projectService.host.createHash(text); } - protected hasFileInfos() { - return !!this.fileInfos_doNotAccessDirectly; - } + function noop() { } - public clear() { - // drop the existing list - it will be re-created as necessary - this.fileInfos_doNotAccessDirectly = undefined; - } + function getNonModuleEmitHandler(): EmitHandler { + return { + addScriptInfo: noop, + removeScriptInfo: noop, + updateScriptInfo: noop, + getFilesAffectedByUpdatedShape + }; - protected getFileInfo(path: Path): T { - return this.getFileInfos().get(path); - } - - protected getOrCreateFileInfo(path: Path): T { - let fileInfo = this.getFileInfo(path); - if (!fileInfo) { - const scriptInfo = this.project.getScriptInfo(path); - fileInfo = new this.ctor(scriptInfo, this.project); - this.setFileInfo(path, fileInfo); - } - return fileInfo; - } - - protected getFileInfoPaths(): Path[] { - return arrayFrom(this.getFileInfos().keys() as Iterator); - } - - protected setFileInfo(path: Path, info: T) { - this.getFileInfos().set(path, info); - } - - protected removeFileInfo(path: Path) { - this.getFileInfos().delete(path); - } - - protected forEachFileInfo(action: (fileInfo: T) => any) { - this.getFileInfos().forEach(action); - } - - abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; - abstract onProjectUpdateGraph(): void; - protected abstract ensureFileInfoIfInProject(scriptInfo: ScriptInfo): void; - - /** - * @returns {boolean} whether the emit was conducted or not - */ - emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean { - this.ensureFileInfoIfInProject(scriptInfo); - const fileInfo = this.getFileInfo(scriptInfo.path); - if (!fileInfo) { - return false; - } - - const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); - if (!emitSkipped) { - const projectRootPath = this.project.getProjectRootPath(); - for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); - writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); - } - } - return !emitSkipped; - } - } - - class NonModuleBuilder extends AbstractBuilder { - - constructor(public readonly project: Project) { - super(project, BuilderFileInfo); - } - - protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) { - if (this.project.containsScriptInfo(scriptInfo)) { - this.getOrCreateFileInfo(scriptInfo.path); - } - } - - onProjectUpdateGraph() { - if (this.hasFileInfos()) { - this.forEachFileInfo(fileInfo => { - if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) { - // This file was deleted from this project - this.removeFileInfo(fileInfo.scriptInfo.path); - } - }); - } - } - - /** - * Note: didn't use path as parameter because the returned file names will be directly - * consumed by the API user, which will use it to interact with file systems. Path - * should only be used internally, because the case sensitivity is not trustable. - */ - getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - const info = this.getOrCreateFileInfo(scriptInfo.path); - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; - if (info.updateShapeSignature()) { - const options = this.project.getCompilerOptions(); + function getFilesAffectedByUpdatedShape(_scriptInfo: ScriptInfo, singleFileResult: string[]): string[] { + const options = project.getCompilerOptions(); // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, // so returning the file itself is good enough. if (options && (options.out || options.outFile)) { return singleFileResult; } - return this.project.getAllEmittableFiles(); - } - return singleFileResult; - } - } - - class ModuleBuilderFileInfo extends BuilderFileInfo { - references = createSortedArray(); - readonly referencedBy = createSortedArray(); - scriptVersionForReferences: string; - - static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison { - return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName); - } - - addReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - } - - removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - } - - removeFileReferences() { - for (const reference of this.references) { - reference.removeReferencedBy(this); - } - clear(this.references); - } - } - - class ModuleBuilder extends AbstractBuilder { - - constructor(public readonly project: Project) { - super(project, ModuleBuilderFileInfo); - } - - private projectVersionForDependencyGraph: string; - - public clear() { - this.projectVersionForDependencyGraph = undefined; - super.clear(); - } - - private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray { - if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return createSortedArray(); - } - - const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos); - } - - protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { - this.ensureProjectDependencyGraphUpToDate(); - } - - onProjectUpdateGraph() { - // Update the graph only if we have computed graph earlier - if (this.hasFileInfos()) { - this.ensureProjectDependencyGraphUpToDate(); + return project.getAllEmittableFiles(); } } - private ensureProjectDependencyGraphUpToDate() { - if (!this.projectVersionForDependencyGraph || this.project.getProjectVersion() !== this.projectVersionForDependencyGraph) { - const currentScriptInfos = this.project.getScriptInfos(); - for (const scriptInfo of currentScriptInfos) { - const fileInfo = this.getOrCreateFileInfo(scriptInfo.path); - this.updateFileReferences(fileInfo); + function getModuleEmitHandler(): EmitHandler { + const references = createMap>(); + const referencedBy = createMultiMap(); + const scriptVersionForReferences = createMap(); + return { + addScriptInfo, + removeScriptInfo, + updateScriptInfo, + getFilesAffectedByUpdatedShape + }; + + function setReferences(path: Path, latestVersion: string, existingMap: Map) { + existingMap = mutateExistingMapWithNewSet(existingMap, project.getReferencedFiles(path), + // Creating new Reference: Also add referenced by + key => { referencedBy.add(key, path); return true; }, + // Remove existing reference + (key, _existingValue) => { referencedBy.remove(key, path); } + ); + references.set(path, existingMap); + scriptVersionForReferences.set(path, latestVersion); + } + + function addScriptInfo(info: ScriptInfo) { + setReferences(info.path, info.getLatestVersion(), undefined); + } + + function removeScriptInfo(path: Path) { + references.delete(path); + scriptVersionForReferences.delete(path); + } + + function updateScriptInfo(scriptInfo: ScriptInfo) { + const path = scriptInfo.path; + const lastUpdatedVersion = scriptVersionForReferences.get(path); + const latestVersion = scriptInfo.getLatestVersion(); + if (lastUpdatedVersion !== latestVersion) { + setReferences(path, latestVersion, references.get(path)); } - this.forEachFileInfo(fileInfo => { - if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) { - // This file was deleted from this project - fileInfo.removeFileReferences(); - this.removeFileInfo(fileInfo.scriptInfo.path); - } - }); - this.projectVersionForDependencyGraph = this.project.getProjectVersion(); - } - } - - private updateFileReferences(fileInfo: ModuleBuilderFileInfo) { - // Only need to update if the content of the file changed. - if (fileInfo.scriptVersionForReferences === fileInfo.scriptInfo.getLatestVersion()) { - return; } - const newReferences = this.getReferencedFileInfos(fileInfo); - const oldReferences = fileInfo.references; - enumerateInsertsAndDeletes(newReferences, oldReferences, - /*inserted*/ newReference => newReference.addReferencedBy(fileInfo), - /*deleted*/ oldReference => { - // New reference is greater then current reference. That means - // the current reference doesn't exist anymore after parsing. So delete - // references. - oldReference.removeReferencedBy(fileInfo); - }, - /*compare*/ ModuleBuilderFileInfo.compareFileInfos); - - fileInfo.references = newReferences; - fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion(); - } - - getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - this.ensureProjectDependencyGraphUpToDate(); - - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; - const fileInfo = this.getFileInfo(scriptInfo.path); - if (!fileInfo || !fileInfo.updateShapeSignature()) { - return singleFileResult; + function getReferencedByPaths(path: Path) { + return referencedBy.get(path) || []; } - if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return this.project.getAllEmittableFiles(); - } + function getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[] { + const path = scriptInfo.path; + const sourceFile = getSourceFile(path); + if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) { + return project.getAllEmittableFiles(); + } - const options = this.project.getCompilerOptions(); - if (options && (options.isolatedModules || options.out || options.outFile)) { - return singleFileResult; - } + const options = project.getCompilerOptions(); + if (options && (options.isolatedModules || options.out || options.outFile)) { + return singleFileResult; + } - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. - // Use slice to clone the array to avoid manipulating in place - const queue = fileInfo.referencedBy.slice(0); - const fileNameSet = createMap(); - fileNameSet.set(scriptInfo.fileName, scriptInfo); - while (queue.length > 0) { - const processingFileInfo = queue.pop(); - if (processingFileInfo.updateShapeSignature() && processingFileInfo.referencedBy.length > 0) { - for (const potentialFileInfo of processingFileInfo.referencedBy) { - if (!fileNameSet.has(potentialFileInfo.scriptInfo.fileName)) { - queue.push(potentialFileInfo); + const fileNamesMap = createMap(); + const setFileName = (path: Path, scriptInfo: ScriptInfo) => { + fileNamesMap.set(path, scriptInfo && shouldEmitFile(scriptInfo) ? scriptInfo.fileName : undefined); + }; + + // Start with the paths this file was referenced by + setFileName(path, scriptInfo); + const queue = getReferencedByPaths(path).slice(); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!fileNamesMap.has(currentPath)) { + const currentScriptInfo = getScriptInfo(currentPath); + if (currentScriptInfo && updateShapeSignature(currentScriptInfo)) { + queue.push(...getReferencedByPaths(currentPath)); } + setFileName(currentPath, currentScriptInfo); } } - fileNameSet.set(processingFileInfo.scriptInfo.fileName, processingFileInfo.scriptInfo); - } - const result: string[] = []; - fileNameSet.forEach((scriptInfo, fileName) => { - if (shouldEmitFile(scriptInfo)) { - result.push(fileName); - } - }); - return result; - } - } - export function createBuilder(project: Project): Builder { - const moduleKind = project.getCompilerOptions().module; - switch (moduleKind) { - case ModuleKind.None: - return new NonModuleBuilder(project); - default: - return new ModuleBuilder(project); + // Return array of values that needs emit + return flatMapIter(fileNamesMap.values(), value => value); + } } } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 003bb87f6a9..ad8e355a500 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -519,6 +519,11 @@ namespace ts.server { return scriptInfo && scriptInfo.getDefaultProject(); } + getScriptInfoEnsuringProjectsUptoDate(uncheckedFileName: string) { + this.ensureInferredProjectsUpToDate(); + return this.getScriptInfo(uncheckedFileName); + } + /** * Ensures the project structures are upto date * @param refreshInferredProjects when true updates the inferred projects even if there is no pending work diff --git a/src/server/project.ts b/src/server/project.ts index 62c450f9f4f..73d7f2d5017 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -811,19 +811,19 @@ namespace ts.server { } } - getReferencedFiles(path: Path): Path[] { + getReferencedFiles(path: Path): Map { + const referencedFiles = createMap(); if (!this.languageServiceEnabled) { - return []; + return referencedFiles; } const sourceFile = this.getSourceFile(path); if (!sourceFile) { - return []; + return referencedFiles; } // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - const referencedFiles = createMap(); if (sourceFile.imports && sourceFile.imports.length > 0) { const checker: TypeChecker = this.program.getTypeChecker(); for (const importName of sourceFile.imports) { @@ -859,8 +859,7 @@ namespace ts.server { }); } - const allFileNames = arrayFrom(referencedFiles.keys()) as Path[]; - return filter(allFileNames, file => this.lsHost.host.fileExists(file)); + return referencedFiles; } // remove a root file from project @@ -1138,12 +1137,6 @@ namespace ts.server { watchWildcards(wildcardDirectories: Map) { this.directoriesWatchedForWildcards = mutateExistingMap( this.directoriesWatchedForWildcards, wildcardDirectories, - // Watcher is same if the recursive flags match - ({ recursive: existingRecursive }, flag) => { - // If the recursive dont match, it needs update - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - return existingRecursive !== recursive; - }, // Create new watch and recursive info (directory, flag) => { const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; @@ -1160,6 +1153,12 @@ namespace ts.server { (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded ), + // Watcher is same if the recursive flags match + ({ recursive: existingRecursive }, flag) => { + // If the recursive dont match, it needs update + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + return existingRecursive !== recursive; + }, // Close existing watch that doesnt match in recursive flag (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged diff --git a/src/server/session.ts b/src/server/session.ts index 0df2dd1e399..1e851d09615 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1193,11 +1193,11 @@ namespace ts.server { } private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] { - const info = this.projectService.getScriptInfo(args.file); + const info = this.projectService.getScriptInfoEnsuringProjectsUptoDate(args.file); const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = []; if (!info) { - return []; + return result; } // if specified a project, we only return affected file list in this project diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 6c884cebf59..36b769182e5 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -312,21 +312,18 @@ namespace ts.server { ): Map { return mutateExistingMap( existingMap, newMap, - // Same value if the value is set in the map - /*isSameValue*/(_existingValue, _valueInNewMap) => true, /*createNewValue*/(key, _valueInNewMap) => createNewValue(key), onDeleteExistingValue, - // Should never be called since we say yes to same values all the time - /*OnDeleteExistingMismatchValue*/(_key, _existingValue) => notImplemented() ); } export function mutateExistingMap( existingMap: Map, newMap: Map, - isSameValue: (existingValue: T, valueInNewMap: U) => boolean, createNewValue: (key: string, valueInNewMap: U) => T, onDeleteExistingValue: (key: string, existingValue: T) => void, - OnDeleteExistingMismatchValue: (key: string, existingValue: T) => void + isSameValue?: (existingValue: T, valueInNewMap: U) => boolean, + OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void, + onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void ): Map { // If there are new values update them if (newMap) { @@ -340,10 +337,13 @@ namespace ts.server { onDeleteExistingValue(key, existingValue); } // different value - remove it - else if (!isSameValue(existingValue, valueInNewMap)) { + else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) { existingMap.delete(key); OnDeleteExistingMismatchValue(key, existingValue); } + else if (onSameExistingValue) { + onSameExistingValue(existingValue, valueInNewMap); + } }); } else { From 499fabc2c1d827f9c468ee9ef9a48553300d2a31 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 18 Jul 2017 17:34:56 -0700 Subject: [PATCH 030/144] Do not update graph in builder if compile on save is not on --- src/server/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/project.ts b/src/server/project.ts index 73d7f2d5017..483dfe841a7 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -594,7 +594,7 @@ namespace ts.server { // update builder only if language service is enabled // otherwise tell it to drop its internal state - if (this.languageServiceEnabled) { + if (this.languageServiceEnabled && this.compileOnSaveEnabled) { this.builder.onProjectUpdateGraph(); } else { From 273569f6fe85412470f80e745cd6ba985b275d99 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 19 Jul 2017 13:07:55 -0700 Subject: [PATCH 031/144] Make the host cache store the fileName instead of undefined for the missing host files --- src/compiler/checker.ts | 2 +- src/compiler/commandLineParser.ts | 20 ++++----- src/compiler/core.ts | 15 +++++-- src/compiler/factory.ts | 8 ++-- src/compiler/moduleNameResolver.ts | 6 +-- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 2 +- src/compiler/utilities.ts | 2 +- src/harness/fourslash.ts | 10 ++--- src/harness/harness.ts | 2 +- src/harness/projectsRunner.ts | 2 +- .../unittests/cachingInServerLSHost.ts | 3 +- .../unittests/configurationExtension.ts | 2 +- src/harness/unittests/telemetry.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 10 ++--- src/server/client.ts | 2 +- src/server/editorServices.ts | 10 ++--- src/services/services.ts | 43 +++++++++++-------- src/services/shims.ts | 2 +- src/services/transpile.ts | 2 +- 20 files changed, 81 insertions(+), 66 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8af11e7803..a578b7208ae 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1161,7 +1161,7 @@ namespace ts { } function diagnosticName(nameArg: __String | Identifier) { - return typeof nameArg === "string" ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); } function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index efb574ac971..5a12f3d0ce4 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -880,7 +880,7 @@ namespace ts { */ export function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { config?: any; error?: Diagnostic } { const textOrDiagnostic = tryReadFile(fileName, readFile); - return typeof textOrDiagnostic === "string" ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; + return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic }; } /** @@ -902,7 +902,7 @@ namespace ts { */ export function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile { const textOrDiagnostic = tryReadFile(fileName, readFile); - return typeof textOrDiagnostic === "string" ? parseJsonText(fileName, textOrDiagnostic) : { parseDiagnostics: [textOrDiagnostic] }; + return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { parseDiagnostics: [textOrDiagnostic] }; } function tryReadFile(fileName: string, readFile: (path: string) => string | undefined): string | Diagnostic { @@ -1106,9 +1106,9 @@ namespace ts { if (!isDoubleQuotedString(valueExpression)) { errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); } - reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string")); + reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")); const text = (valueExpression).text; - if (option && typeof option.type !== "string") { + if (option && !isString(option.type)) { const customOption = option; // Validate custom option type if (!customOption.type.has(text)) { @@ -1179,7 +1179,7 @@ namespace ts { function getCompilerOptionValueTypeString(option: CommandLineOption) { return option.type === "list" ? "Array" : - typeof option.type === "string" ? option.type : "string"; + isString(option.type) ? option.type : "string"; } function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue { @@ -1187,7 +1187,7 @@ namespace ts { if (option.type === "list") { return isArray(value); } - const expectedType = typeof option.type === "string" ? option.type : "string"; + const expectedType = isString(option.type) ? option.type : "string"; return typeof value === expectedType; } } @@ -1571,7 +1571,7 @@ namespace ts { let extendedConfigPath: Path; if (json.extends) { - if (typeof json.extends !== "string") { + if (!isString(json.extends)) { errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); } else { @@ -1796,7 +1796,7 @@ namespace ts { if (optType === "list" && isArray(value)) { return convertJsonOptionOfListType(opt, value, basePath, errors); } - else if (typeof optType !== "string") { + else if (!isString(optType)) { return convertJsonOptionOfCustomType(opt, value, errors); } return normalizeNonListOptionValue(opt, basePath, value); @@ -1809,12 +1809,12 @@ namespace ts { function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { if (option.type === "list") { const listOption = option; - if (listOption.element.isFilePath || typeof listOption.element.type !== "string") { + if (listOption.element.isFilePath || !isString(listOption.element.type)) { return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v); } return value; } - else if (typeof option.type !== "string") { + else if (!isString(option.type)) { return option.type.get(value); } return normalizeNonListOptionValue(option, basePath, value); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 54b772cc29c..2bec904437e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1201,6 +1201,13 @@ namespace ts { return Array.isArray ? Array.isArray(value) : value instanceof Array; } + /** + * Tests whether a value is string + */ + export function isString(text: any): text is string { + return typeof text === "string"; + } + export function tryCast(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined { return value !== undefined && test(value) ? value : undefined; } @@ -1454,16 +1461,16 @@ namespace ts { function compareMessageText(text1: string | DiagnosticMessageChain, text2: string | DiagnosticMessageChain): Comparison { while (text1 && text2) { // We still have both chains. - const string1 = typeof text1 === "string" ? text1 : text1.messageText; - const string2 = typeof text2 === "string" ? text2 : text2.messageText; + const string1 = isString(text1) ? text1 : text1.messageText; + const string2 = isString(text2) ? text2 : text2.messageText; const res = compareValues(string1, string2); if (res) { return res; } - text1 = typeof text1 === "string" ? undefined : text1.next; - text2 = typeof text2 === "string" ? undefined : text2.next; + text1 = isString(text1) ? undefined : text1.next; + text2 = isString(text2) ? undefined : text2.next; } if (!text1 && !text2) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 34727e4c414..ac889ba9c04 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -81,7 +81,7 @@ namespace ts { if (typeof value === "boolean") { return value ? createTrue() : createFalse(); } - if (typeof value === "string") { + if (isString(value)) { return createStringLiteral(value); } return createLiteralFromNode(value); @@ -2130,7 +2130,7 @@ namespace ts { export function createCatchClause(variableDeclaration: string | VariableDeclaration, block: Block) { const node = createSynthesizedNode(SyntaxKind.CatchClause); - node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration; + node.variableDeclaration = isString(variableDeclaration) ? createVariableDeclaration(variableDeclaration) : variableDeclaration; node.block = block; return node; } @@ -2438,11 +2438,11 @@ namespace ts { function asName(name: string | EntityName): EntityName; function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode; function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) { - return typeof name === "string" ? createIdentifier(name) : name; + return isString(name) ? createIdentifier(name) : name; } function asExpression(value: string | number | Expression) { - return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value; + return isString(value) || typeof value === "number" ? createLiteral(value) : value; } function asNodeArray(array: ReadonlyArray | undefined): NodeArray | undefined { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 513c741ba09..027d7ca2025 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -68,7 +68,7 @@ namespace ts { } const fileName = jsonContent[fieldName]; - if (typeof fileName !== "string") { + if (!isString(fileName)) { if (state.traceEnabled) { trace(state.host, Diagnostics.Expected_type_of_0_field_in_package_json_to_be_string_got_1, fieldName, typeof fileName); } @@ -630,8 +630,8 @@ namespace ts { } if (matchedPattern) { - const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName); - const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern); + const matchedStar = isString(matchedPattern) ? undefined : matchedText(matchedPattern, moduleName); + const matchedPatternText = isString(matchedPattern) ? matchedPattern : patternText(matchedPattern); if (state.traceEnabled) { trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 64e8eb0c803..5999329a14c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -337,7 +337,7 @@ namespace ts { } export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string { - if (typeof messageText === "string") { + if (isString(messageText)) { return messageText; } else { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 89cfb074bff..8533ec95f3d 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -184,7 +184,7 @@ namespace ts { function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) { // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" - const fileName = typeof relativeFileName !== "string" + const fileName = !isString(relativeFileName) ? undefined : ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath); // Some applications save a working file via rename operations diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 088733e21ef..facb0bdc2ee 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -355,7 +355,7 @@ namespace ts { } export function getTextOfConstantValue(value: string | number) { - return typeof value === "string" ? '"' + escapeNonAsciiString(value) + '"' : "" + value; + return isString(value) ? '"' + escapeNonAsciiString(value) + '"' : "" + value; } // Add an extra underscore to identifiers that start with two underscores to avoid issues with magic names like '__proto__' diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c6ec0f257b6..cd501b43959 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -391,7 +391,7 @@ namespace FourSlash { // Entry points from fourslash.ts public goToMarker(name: string | Marker = "") { - const marker = typeof name === "string" ? this.getMarkerByName(name) : name; + const marker = ts.isString(name) ? this.getMarkerByName(name) : name; if (this.activeFile.fileName !== marker.fileName) { this.openFile(marker.fileName); } @@ -400,7 +400,7 @@ namespace FourSlash { if (marker.position === -1 || marker.position > content.length) { throw new Error(`Marker "${name}" has been invalidated by unrecoverable edits to the file.`); } - const mName = typeof name === "string" ? name : this.markerName(marker); + const mName = ts.isString(name) ? name : this.markerName(marker); this.lastKnownMarker = mName; this.goToPosition(marker.position); } @@ -1028,7 +1028,7 @@ namespace FourSlash { public verifyNoReferences(markerNameOrRange?: string | Range) { if (markerNameOrRange) { - if (typeof markerNameOrRange === "string") { + if (ts.isString(markerNameOrRange)) { this.goToMarker(markerNameOrRange); } else { @@ -1524,7 +1524,7 @@ namespace FourSlash { resultString += "Diagnostics:" + Harness.IO.newLine(); const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram()); for (const diagnostic of diagnostics) { - if (typeof diagnostic.messageText !== "string") { + if (!ts.isString(diagnostic.messageText)) { let chainedMessage = diagnostic.messageText; let indentation = " "; while (chainedMessage) { @@ -2858,7 +2858,7 @@ namespace FourSlash { result = this.testData.files[index]; } } - else if (typeof indexOrName === "string") { + else if (ts.isString(indexOrName)) { let name = indexOrName; // names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 55a8f3ebb4d..8f4dae70116 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -225,7 +225,7 @@ namespace Utils { return JSON.stringify(file, (_, v) => isNodeOrArray(v) ? serializeNode(v) : v, " "); function getKindName(k: number | string): string { - if (typeof k === "string") { + if (ts.isString(k)) { return k; } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 7344c432b97..fd8d06427cf 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -254,7 +254,7 @@ class ProjectRunner extends RunnerBase { if (option) { const optType = option.type; let value = testCase[name]; - if (typeof optType !== "string") { + if (!ts.isString(optType)) { const key = value.toLowerCase(); const optTypeValue = optType.get(key); if (optTypeValue) { diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index eb2907e89de..5265a12355e 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -199,7 +199,8 @@ namespace ts { let diags = project.getLanguageService().getSemanticDiagnostics(root.name); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); assert.isTrue(diags.length === 1, "one diagnostic expected"); - assert.isTrue(typeof diags[0].messageText === "string" && ((diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message"); + const messageText = diags[0].messageText; + assert.isTrue(isString(messageText) && messageText.indexOf("Cannot find module") === 0, "should be 'cannot find module' message"); fileMap.set(imported.name, imported); fileExistsCalledForBar = false; diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 2d50d2cb2af..f3a5e930457 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -87,7 +87,7 @@ namespace ts { "/dev/tests/scenarios/first.json": "", "/dev/tests/baselines/first/output.ts": "" }); - const testContents = mapEntries(testContentsJson, (k, v) => [k, typeof v === "string" ? v : JSON.stringify(v)]); + const testContents = mapEntries(testContentsJson, (k, v) => [k, isString(v) ? v : JSON.stringify(v)]); const caseInsensitiveBasePath = "c:/dev/"; const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content])); diff --git a/src/harness/unittests/telemetry.ts b/src/harness/unittests/telemetry.ts index 7f3428c8393..a8562503074 100644 --- a/src/harness/unittests/telemetry.ts +++ b/src/harness/unittests/telemetry.ts @@ -282,7 +282,7 @@ namespace ts.projectSystem { } function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder { - return { path, content: typeof content === "string" ? "" : JSON.stringify(content) }; + return { path, content: isString(content) ? "" : JSON.stringify(content) }; } function fileStats(nonZeroStats: Partial): server.FileStats { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fa6ffc74e65..ca86138803c 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -101,7 +101,7 @@ namespace ts.projectSystem { } addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { - const out = typeof stdout === "string" ? stdout : createNpmPackageJsonString(stdout); + const out = isString(stdout) ? stdout : createNpmPackageJsonString(stdout); const action: PostExecAction = { success: !!out, callback: cb @@ -258,7 +258,7 @@ namespace ts.projectSystem { } export function isFile(s: FSEntry): s is File { - return s && typeof (s).content === "string"; + return s && isString((s).content); } function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) { @@ -413,7 +413,7 @@ namespace ts.projectSystem { const currentEntry = this.fs.get(path); if (currentEntry) { if (isFile(currentEntry)) { - if (typeof fileOrFolder.content === "string") { + if (isString(fileOrFolder.content)) { // Update file if (currentEntry.content !== fileOrFolder.content) { currentEntry.content = fileOrFolder.content; @@ -426,7 +426,7 @@ namespace ts.projectSystem { } else { // Folder - if (typeof fileOrFolder.content === "string") { + if (isString(fileOrFolder.content)) { // TODO: Changing from folder => file } else { @@ -453,7 +453,7 @@ namespace ts.projectSystem { } ensureFileOrFolder(fileOrFolder: FileOrFolder) { - if (typeof fileOrFolder.content === "string") { + if (isString(fileOrFolder.content)) { const file = this.toFile(fileOrFolder); Debug.assert(!this.fs.get(file.path)); const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); diff --git a/src/server/client.ts b/src/server/client.ts index a7e0615dce8..4d23ca05805 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -342,7 +342,7 @@ namespace ts.server { convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic { let category: DiagnosticCategory; for (const id in DiagnosticCategory) { - if (typeof id === "string" && entry.category === id.toLowerCase()) { + if (isString(id) && entry.category === id.toLowerCase()) { category = (DiagnosticCategory)[id]; } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index ad8e355a500..0b5cb8c119a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -159,7 +159,7 @@ namespace ts.server { }; export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings { - if (typeof protocolOptions.indentStyle === "string") { + if (isString(protocolOptions.indentStyle)) { protocolOptions.indentStyle = indentStyle.get(protocolOptions.indentStyle.toLowerCase()); Debug.assert(protocolOptions.indentStyle !== undefined); } @@ -169,7 +169,7 @@ namespace ts.server { export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin { compilerOptionConverters.forEach((mappedValues, id) => { const propertyValue = protocolOptions[id]; - if (typeof propertyValue === "string") { + if (isString(propertyValue)) { protocolOptions[id] = mappedValues.get(propertyValue.toLowerCase()); } }); @@ -177,9 +177,7 @@ namespace ts.server { } export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind { - return typeof scriptKindName === "string" - ? convertScriptKindName(scriptKindName) - : scriptKindName; + return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName; } export function convertScriptKindName(scriptKindName: protocol.ScriptKindName) { @@ -1947,7 +1945,7 @@ namespace ts.server { // RegExp group numbers are 1-based, but the first element in groups // is actually the original string, so it all works out in the end. if (typeof groupNumberOrString === "number") { - if (typeof groups[groupNumberOrString] !== "string") { + if (!isString(groups[groupNumberOrString])) { // Specification was wrong - exclude nothing! this.logger.info(`Incorrect RegExp specification in safelist rule ${name} - not enough groups`); // * can't appear in a filename; escape it because it's feeding into a RegExp diff --git a/src/services/services.ts b/src/services/services.ts index 2e031f3cad2..fb87c0065d5 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -812,18 +812,21 @@ namespace ts { return codefix.getSupportedErrorCodes(); } + // Either it will be file name if host doesnt have file or it will be the host's file information + type CachedHostFileInformation = HostFileInformation | string; + // Cache host information about script Should be refreshed // at each language service public entry point, since we don't know when // the set of scripts handled by the host changes. class HostCache { - private fileNameToEntry: Map; + private fileNameToEntry: Map; private _compilationSettings: CompilerOptions; private currentDirectory: string; constructor(private host: LanguageServiceHost, getCanonicalFileName: (fileName: string) => string) { // script id => script index this.currentDirectory = host.getCurrentDirectory(); - this.fileNameToEntry = createMap(); + this.fileNameToEntry = createMap(); // Initialize the list with the root file names const rootFileNames = host.getScriptFileNames(); @@ -840,7 +843,7 @@ namespace ts { } private createEntry(fileName: string, path: Path) { - let entry: HostFileInformation; + let entry: CachedHostFileInformation; const scriptSnapshot = this.host.getScriptSnapshot(fileName); if (scriptSnapshot) { entry = { @@ -850,36 +853,41 @@ namespace ts { scriptKind: getScriptKind(fileName, this.host) }; } + else { + entry = fileName; + } this.fileNameToEntry.set(path, entry); return entry; } - public getEntryByPath(path: Path): HostFileInformation { + public getEntryByPath(path: Path): CachedHostFileInformation | undefined { return this.fileNameToEntry.get(path); } - public containsEntryByPath(path: Path): boolean { - return this.fileNameToEntry.has(path); + public getHostFileInformation(path: Path): HostFileInformation | undefined { + const entry = this.fileNameToEntry.get(path); + return !isString(entry) ? entry : undefined; } public getOrCreateEntryByPath(fileName: string, path: Path): HostFileInformation { - return this.containsEntryByPath(path) - ? this.getEntryByPath(path) - : this.createEntry(fileName, path); + const info = this.getEntryByPath(path) || this.createEntry(fileName, path); + return isString(info) ? undefined : info; } public getRootFileNames(): string[] { - return this.host.getScriptFileNames(); + return arrayFrom(this.fileNameToEntry.values(), entry => { + return isString(entry) ? entry : entry.hostFileName; + }); } public getVersion(path: Path): string { - const file = this.getEntryByPath(path); + const file = this.getHostFileInformation(path); return file && file.version; } public getScriptSnapshot(path: Path): IScriptSnapshot { - const file = this.getEntryByPath(path); + const file = this.getHostFileInformation(path); return file && file.scriptSnapshot; } } @@ -1145,16 +1153,17 @@ namespace ts { fileExists: (fileName): boolean => { // stub missing host functionality const path = toPath(fileName, currentDirectory, getCanonicalFileName); - return hostCache.containsEntryByPath(path) ? - !!hostCache.getEntryByPath(path) : + const entry = hostCache.getEntryByPath(path); + return entry ? + !isString(entry) : (host.fileExists && host.fileExists(fileName)); }, readFile(fileName) { // stub missing host functionality const path = toPath(fileName, currentDirectory, getCanonicalFileName); - if (hostCache.containsEntryByPath(path)) { - const entry = hostCache.getEntryByPath(path); - return entry && entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); + const entry = hostCache.getEntryByPath(path); + if (entry) { + return isString(entry) ? undefined : entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); } return host.readFile && host.readFile(fileName); }, diff --git a/src/services/shims.ts b/src/services/shims.ts index b7c85d2ce82..574ec70d365 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -522,7 +522,7 @@ namespace ts { if (logPerformance) { const end = timestamp(); logger.log(`${actionDescription} completed in ${end - start} msec`); - if (typeof result === "string") { + if (isString(result)) { let str = result; if (str.length > 128) { str = str.substring(0, 128) + "..."; diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 561c188c6cd..fc381ba8e50 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -139,7 +139,7 @@ namespace ts { const value = options[opt.name]; // Value should be a key of opt.type - if (typeof value === "string") { + if (isString(value)) { // If value is not a string, this will fail options[opt.name] = parseCustomTypeOption(opt, value, diagnostics); } From 94a589b3bb72d4e886daa0829521c1e3605138be Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 21 Jul 2017 14:58:06 -0700 Subject: [PATCH 032/144] Program cannot be reused if the missing file is now present Also dont update filesByName array just to update missing file paths --- src/compiler/program.ts | 27 ++++++++----------- .../unittests/reuseProgramStructure.ts | 2 +- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 5999329a14c..8bb7427fa2f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -473,6 +473,7 @@ namespace ts { } const filesByName = createMap(); + let missingFilePaths: Path[]; // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; @@ -511,9 +512,9 @@ namespace ts { }); } } - } - const missingFilePaths = arrayFrom(filesByName.keys(), p => p).filter(p => !filesByName.get(p)); + missingFilePaths = arrayFrom(filesByName.keys(), p => p).filter(p => !filesByName.get(p)); + } // unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks moduleResolutionCache = undefined; @@ -773,6 +774,13 @@ namespace ts { const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; oldProgram.structureIsReused = StructureIsReused.Completely; + // If the missing file paths are now present, it can change the progam structure, + // and hence cant reuse the structure. + // This is same as how we dont reuse the structure if one of the file from old program is now missing + if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { + return oldProgram.structureIsReused = StructureIsReused.Not; + } + for (const oldSourceFile of oldProgram.getSourceFiles()) { const newSourceFile = host.getSourceFileByPath ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target) @@ -869,20 +877,7 @@ namespace ts { return oldProgram.structureIsReused; } - // If a file has ceased to be missing, then we need to discard some of the old - // structure in order to pick it up. - // Caution: if the file has created and then deleted between since it was discovered to - // be missing, then the corresponding file watcher will have been closed and no new one - // will be created until we encounter a change that prevents complete structure reuse. - // During this interval, creation of the file will go unnoticed. We expect this to be - // both rare and low-impact. - if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) { - return oldProgram.structureIsReused = StructureIsReused.SafeModules; - } - - for (const p of oldProgram.getMissingFilePaths()) { - filesByName.set(p, undefined); - } + missingFilePaths = oldProgram.getMissingFilePaths(); // update fileName -> file mapping for (let i = 0; i < newSourceFiles.length; i++) { diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index de5f5a756d8..a434ed8025d 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -338,7 +338,7 @@ namespace ts { const program_2 = updateProgram(program_1, ["a.ts"], options, noop, newTexts); assert.deepEqual(emptyArray, program_2.getMissingFilePaths()); - assert.equal(StructureIsReused.SafeModules, program_1.structureIsReused); + assert.equal(StructureIsReused.Not, program_1.structureIsReused); }); it("resolution cache follows imports", () => { From ef5935b52ca1b4e14e03710b052d84b1b24d27de Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 24 Jul 2017 16:57:49 -0700 Subject: [PATCH 033/144] Initial refactoring so that watch from tsc follows the tsserver projects --- src/compiler/commandLineParser.ts | 2 +- src/compiler/core.ts | 164 +++++++ src/compiler/program.ts | 126 +++++- src/compiler/tsc.ts | 705 ++++++++++++++++++++++-------- src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 93 ++++ src/server/editorServices.ts | 2 +- src/server/lsHost.ts | 111 +---- src/server/project.ts | 94 ++-- src/server/utilities.ts | 73 ---- src/services/services.ts | 92 +--- src/services/utilities.ts | 20 - 12 files changed, 970 insertions(+), 518 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 5a12f3d0ce4..e3348892844 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1979,7 +1979,7 @@ namespace ts { * @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: ReadonlyArray): ExpandResult { + export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray = []): ExpandResult { basePath = normalizePath(basePath); const keyMapper = host.useCaseSensitiveFileNames ? caseSensitiveKeyMapper : caseInsensitiveKeyMapper; diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 2bec904437e..cbccdcbed02 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2559,4 +2559,168 @@ namespace ts { export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } + + export interface HostForCaching { + useCaseSensitiveFileNames: boolean; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + } + + export interface CachedHost { + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + addOrDeleteFileOrFolder(fileOrFolder: string): void; + clearCache(): void; + } + + export function createCachedHost(host: HostForCaching): CachedHost { + const cachedReadDirectoryResult = createMap(); + const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + return { + writeFile, + fileExists, + directoryExists, + createDirectory, + getCurrentDirectory, + getDirectories, + readDirectory, + addOrDeleteFileOrFolder, + clearCache + }; + + function toPath(fileName: string) { + return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); + } + + function getFileSystemEntries(rootDir: string) { + const path = toPath(rootDir); + const cachedResult = cachedReadDirectoryResult.get(path); + if (cachedResult) { + return cachedResult; + } + + const resultFromHost: FileSystemEntries = { + files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], + directories: host.getDirectories(rootDir) || [] + }; + + cachedReadDirectoryResult.set(path, resultFromHost); + return resultFromHost; + } + + function canWorkWithCacheForDir(rootDir: string) { + // Some of the hosts might not be able to handle read directory or getDirectories + const path = toPath(rootDir); + if (cachedReadDirectoryResult.get(path)) { + return true; + } + try { + return getFileSystemEntries(rootDir); + } + catch (_e) { + return false; + } + } + + function fileNameEqual(name1: string, name2: string) { + return getCanonicalFileName(name1) === getCanonicalFileName(name2); + } + + function hasEntry(entries: ReadonlyArray, name: string) { + return some(entries, file => fileNameEqual(file, name)); + } + + function updateFileSystemEntry(entries: ReadonlyArray, baseName: string, isValid: boolean) { + if (hasEntry(entries, baseName)) { + if (!isValid) { + return filter(entries, entry => !fileNameEqual(entry, baseName)); + } + } + else if (isValid) { + return entries.concat(baseName); + } + return entries; + } + + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + const path = toPath(fileName); + const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); + const baseFileName = getBaseFileName(normalizePath(fileName)); + if (result) { + result.files = updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true); + } + return host.writeFile(fileName, data, writeByteOrderMark); + } + + function fileExists(fileName: string): boolean { + const path = toPath(fileName); + const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); + const baseName = getBaseFileName(normalizePath(fileName)); + return (result && hasEntry(result.files, baseName)) || host.fileExists(fileName); + } + + function directoryExists(dirPath: string): boolean { + const path = toPath(dirPath); + return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); + } + + function createDirectory(dirPath: string) { + const path = toPath(dirPath); + const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); + const baseFileName = getBaseFileName(path); + if (result) { + result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + } + host.createDirectory(dirPath); + } + + function getDirectories(rootDir: string): string[] { + if (canWorkWithCacheForDir(rootDir)) { + return getFileSystemEntries(rootDir).directories.slice(); + } + return host.getDirectories(rootDir); + } + function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { + if (canWorkWithCacheForDir(rootDir)) { + return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, path => getFileSystemEntries(path)); + } + return host.readDirectory(rootDir, extensions, excludes, includes, depth); + } + + function addOrDeleteFileOrFolder(fileOrFolder: string) { + const path = toPath(fileOrFolder); + const existingResult = cachedReadDirectoryResult.get(path); + if (existingResult) { + if (!host.directoryExists(fileOrFolder)) { + cachedReadDirectoryResult.delete(path); + } + } + else { + // Was this earlier file + const parentResult = cachedReadDirectoryResult.get(getDirectoryPath(path)); + if (parentResult) { + const baseName = getBaseFileName(fileOrFolder); + if (parentResult) { + parentResult.files = updateFileSystemEntry(parentResult.files, baseName, host.fileExists(path)); + parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(path)); + } + } + } + } + + function clearCache() { + cachedReadDirectoryResult.clear(); + } + } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8bb7427fa2f..0c319b18053 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -386,6 +386,114 @@ namespace ts { allDiagnostics?: Diagnostic[]; } + export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string): boolean { + // If we haven't create a program yet, then it is not up-to-date + if (!program) { + return false; + } + + // If number of files in the program do not match, it is not up-to-date + if (program.getRootFileNames().length !== rootFileNames.length) { + return false; + } + + const fileNames = concatenate(rootFileNames, map(program.getSourceFiles(), sourceFile => sourceFile.fileName)); + // If any file is not up-to-date, then the whole program is not up-to-date + for (const fileName of fileNames) { + if (!sourceFileUpToDate(program.getSourceFile(fileName))) { + return false; + } + } + + const currentOptions = program.getCompilerOptions(); + // If the compilation settings do no match, then the program is not up-to-date + if (!compareDataObjects(currentOptions, newOptions)) { + return false; + } + + // If everything matches but the text of config file is changed, + // error locations can change for program options, so update the program + if (currentOptions.configFile && newOptions.configFile) { + return currentOptions.configFile.text === newOptions.configFile.text; + } + + return true; + + function sourceFileUpToDate(sourceFile: SourceFile): boolean { + if (!sourceFile) { + return false; + } + return sourceFile.version === getSourceVersion(sourceFile.path); + } + } + + function shouldProgramCreateNewSourceFiles(program: Program, newOptions: CompilerOptions) { + // If any of these options change, we cant reuse old source file even if version match + const oldOptions = program && program.getCompilerOptions(); + return oldOptions && + (oldOptions.target !== newOptions.target || + oldOptions.module !== newOptions.module || + oldOptions.moduleResolution !== newOptions.moduleResolution || + oldOptions.noResolve !== newOptions.noResolve || + oldOptions.jsx !== newOptions.jsx || + oldOptions.allowJs !== newOptions.allowJs || + oldOptions.disableSizeLimit !== newOptions.disableSizeLimit || + oldOptions.baseUrl !== newOptions.baseUrl || + !equalOwnProperties(oldOptions.paths, newOptions.paths)); + } + + /** + * Updates the existing missing file watches with the new set of missing files after new program is created + * @param program + * @param existingMap + * @param createMissingFileWatch + * @param closeExistingFileWatcher + */ + export function updateMissingFilePathsWatch(program: Program, existingMap: Map, + createMissingFileWatch: (missingFilePath: Path) => FileWatcher, + closeExistingFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) { + + const missingFilePaths = program.getMissingFilePaths(); + const newMissingFilePathMap = arrayToSet(missingFilePaths); + // Update the missing file paths watcher + return mutateExistingMapWithNewSet( + existingMap, newMissingFilePathMap, + // Watch the missing files + createMissingFileWatch, + // Files that are no longer missing (e.g. because they are no longer required) + // should no longer be watched. + closeExistingFileWatcher + ); + } + + export type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean }; + + export function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map, wildcardDirectories: Map, + watchDirectory: (directory: string, recursive: boolean) => FileWatcher, + closeDirectoryWatcher: (directory: string, watcher: FileWatcher, recursive: boolean, recursiveChanged: boolean) => void) { + return mutateExistingMap( + existingWatchedForWildcards, wildcardDirectories, + // Create new watch and recursive info + (directory, flag) => { + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + return { + watcher: watchDirectory(directory, recursive), + recursive + }; + }, + // Close existing watch thats not needed any more + (directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ false), + // Watcher is same if the recursive flags match + ({ recursive: existingRecursive }, flag) => { + // If the recursive dont match, it needs update + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + return existingRecursive !== recursive; + }, + // Close existing watch that doesnt match in recursive flag + (directory, { watcher, recursive }) => closeDirectoryWatcher(directory, watcher, recursive, /*recursiveChanged*/ true) + ); + } + /** * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' * that represent a compilation unit. @@ -478,6 +586,7 @@ namespace ts { // used to track cases when two file names differ only in casing const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; + const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); if (structuralIsReused !== StructureIsReused.Completely) { forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false)); @@ -519,6 +628,17 @@ namespace ts { // unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks moduleResolutionCache = undefined; + // Release any files we have acquired in the old program but are + // not part of the new program. + if (oldProgram && host.onReleaseOldSourceFile) { + const oldSourceFiles = oldProgram.getSourceFiles(); + for (const oldSourceFile of oldSourceFiles) { + if (!getSourceFile(oldSourceFile.path) || shouldCreateNewSourceFile) { + host.onReleaseOldSourceFile(oldSourceFile, oldProgram.getCompilerOptions()); + } + } + } + // unconditionally set oldProgram to undefined to prevent it from being captured in closure oldProgram = undefined; @@ -783,8 +903,8 @@ namespace ts { for (const oldSourceFile of oldProgram.getSourceFiles()) { const newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target) - : host.getSourceFile(oldSourceFile.fileName, options.target); + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target, /*onError*/ undefined, shouldCreateNewSourceFile) + : host.getSourceFile(oldSourceFile.fileName, options.target, /*onError*/ undefined, shouldCreateNewSourceFile); if (!newSourceFile) { return oldProgram.structureIsReused = StructureIsReused.Not; @@ -1593,7 +1713,7 @@ namespace ts { else { fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); } - }); + }, shouldCreateNewSourceFile); filesByName.set(path, file); if (file) { diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index db25afe45b6..cf793919a4c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -98,22 +98,9 @@ namespace ts { export function executeCommandLine(args: string[]): void { const commandLine = parseCommandLine(args); - let configFileName: string; // Configuration file name (if any) - let cachedConfigFileText: string; // Cached configuration file text, used for reparsing (if any) - let configFileWatcher: FileWatcher; // Configuration file watcher - let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal - let cachedProgram: Program; // Program cached from last compilation - let rootFileNames: string[]; // Root fileNames for compilation - let compilerOptions: CompilerOptions; // Compiler options for compilation - let compilerHost: CompilerHost; // Compiler host - let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host - let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation - let timerHandleForDirectoryChanges: any; // Handle for 0.25s wait timer to trigger directory change handler - // This map stores and reuses results of fileExists check that happen inside 'createProgram' - // This allows to save time in module resolution heavy scenarios when existence of the same file might be checked multiple times. - let cachedExistingFiles: Map; - let hostFileExists: typeof compilerHost.fileExists; + // Configuration file name (if any) + let configFileName: string; if (commandLine.options.locale) { if (!isJSONSupported()) { @@ -126,7 +113,7 @@ namespace ts { // If there are any errors due to command line parsing and/or // setting up localization, report them and quit. if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, compilerHost); + reportDiagnostics(commandLine.errors, /*host*/ undefined); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -183,232 +170,570 @@ namespace ts { return sys.exit(ExitStatus.Success); } - if (isWatchSet(commandLine.options)) { - if (!sys.watchFile) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - if (configFileName) { - configFileWatcher = sys.watchFile(configFileName, configFileChanged); - } - if (sys.watchDirectory && configFileName) { - const directory = ts.getDirectoryPath(configFileName); - directoryWatcher = sys.watchDirectory( - // When the configFileName is just "tsconfig.json", the watched directory should be - // the current directory; if there is a given "project" parameter, then the configFileName - // is an absolute file name. - directory === "" ? "." : directory, - watchedDirectoryChanged, /*recursive*/ true); - } - } - - performCompilation(); - - function parseConfigFile(): ParsedCommandLine { - if (!cachedConfigFileText) { - try { - cachedConfigFileText = sys.readFile(configFileName); - } - catch (e) { - const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - } - if (!cachedConfigFileText) { - const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], /* compilerHost */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - - const result = parseJsonText(configFileName, cachedConfigFileText); - reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); - - const cwd = sys.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); - + if (configFileName) { + const configParseResult = parseConfigFile(configFileName, commandLine, sys); + const { fileNames, options } = configParseResult; if (isWatchSet(configParseResult.options)) { - if (!sys.watchFile) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - if (!directoryWatcher && sys.watchDirectory && configFileName) { - const directory = ts.getDirectoryPath(configFileName); - directoryWatcher = sys.watchDirectory( - // When the configFileName is just "tsconfig.json", the watched directory should be - // the current directory; if there is a given "project" parameter, then the configFileName - // is an absolute file name. - directory === "" ? "." : directory, - watchedDirectoryChanged, /*recursive*/ true); - } + reportWatchModeWithoutSysSupport(); + createWatchMode(commandLine, configFileName, fileNames, options, configParseResult.configFileSpecs, configParseResult.wildcardDirectories); } - return configParseResult; + else { + performCompilation(fileNames, options); + } + } + else if (isWatchSet(commandLine.options)) { + reportWatchModeWithoutSysSupport(); + createWatchMode(commandLine); + } + else { + performCompilation(commandLine.fileNames, commandLine.options); } - // Invoked to perform initial compilation or re-compilation in watch mode - function performCompilation() { - - if (!cachedProgram) { - if (configFileName) { - const configParseResult = parseConfigFile(); - rootFileNames = configParseResult.fileNames; - compilerOptions = configParseResult.options; - } - else { - rootFileNames = commandLine.fileNames; - compilerOptions = commandLine.options; - } - compilerHost = createCompilerHost(compilerOptions); - hostGetSourceFile = compilerHost.getSourceFile; - compilerHost.getSourceFile = getSourceFile; - - hostFileExists = compilerHost.fileExists; - compilerHost.fileExists = cachedFileExists; + function reportWatchModeWithoutSysSupport() { + if (!sys.watchFile || !sys.watchDirectory) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); + sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } + } + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { if (compilerOptions.pretty) { reportDiagnosticWorker = reportDiagnosticWithColorAndContext; } - // reset the cache of existing files - cachedExistingFiles = createMap(); - + const compilerHost = createCompilerHost(compilerOptions); const compileResult = compile(rootFileNames, compilerOptions, compilerHost); + return sys.exit(compileResult.exitStatus); + } + } - if (!isWatchSet(compilerOptions)) { - return sys.exit(compileResult.exitStatus); + interface HostFileInfo { + version: number; + sourceFile: SourceFile; + fileWatcher: FileWatcher; + } + + function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike) { + let program: Program; + let needsReload: boolean; + let missingFilesMap: Map; + let configFileWatcher: FileWatcher; + let watchedWildCardDirectories: Map; + let timerToUpdateProgram: any; + + let compilerOptions: CompilerOptions; + let rootFileNames: string[]; + + const sourceFilesCache = createMap(); + + let host: System; + if (configFileName) { + rootFileNames = configFileRootFiles; + compilerOptions = configFileOptions; + host = createCachedSystem(sys); + configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged); + } + else { + rootFileNames = commandLine.fileNames; + compilerOptions = commandLine.options; + host = sys; + } + const currentDirectory = host.getCurrentDirectory(); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + + if (compilerOptions.pretty) { + reportDiagnosticWorker = reportDiagnosticWithColorAndContext; + } + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + + function synchronizeProgram() { + writeLog(`Synchronizing program`); + + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) { + return; } - setCachedProgram(compileResult.program); + // Create the compiler host + const compilerHost = createWatchedCompilerHost(compilerOptions); + program = compile(rootFileNames, compilerOptions, compilerHost, program).program; + + // Update watches + missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); - - const missingPaths = compileResult.program.getMissingFilePaths(); - missingPaths.forEach(path => { - const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => { - if (eventKind === FileWatcherEventKind.Created) { - fileWatcher.close(); - startTimerForRecompilation(); - } - }); - }); } - function cachedFileExists(fileName: string): boolean { - let fileExists = cachedExistingFiles.get(fileName); - if (fileExists === undefined) { - cachedExistingFiles.set(fileName, fileExists = hostFileExists(fileName)); + function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { + const existingDirectories = createMap(); + function directoryExists(directoryPath: string): boolean { + if (existingDirectories.has(directoryPath)) { + return true; + } + if (host.directoryExists(directoryPath)) { + existingDirectories.set(directoryPath, true); + return true; + } + return false; } - return fileExists; - } - function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) { - // Return existing SourceFile object if one is available - if (cachedProgram) { - const sourceFile = cachedProgram.getSourceFile(fileName); - // A modified source file has no watcher and should not be reused - if (sourceFile && sourceFile.fileWatcher) { - return sourceFile; + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); } } - // Use default host function - const sourceFile = hostGetSourceFile(fileName, languageVersion, onError); - if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) { - // Attach a file watcher - sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName, eventKind) => sourceFileChanged(sourceFile, eventKind)); - } - return sourceFile; - } - // Change cached program to the given program - function setCachedProgram(program: Program) { - if (cachedProgram) { - const newSourceFiles = program ? program.getSourceFiles() : undefined; - forEach(cachedProgram.getSourceFiles(), sourceFile => { - if (!(newSourceFiles && contains(newSourceFiles, sourceFile))) { - if (sourceFile.fileWatcher) { - sourceFile.fileWatcher.close(); - sourceFile.fileWatcher = undefined; - } + type OutputFingerprint = { + hash: string; + byteOrderMark: boolean; + mtime: Date; + }; + let outputFingerprints: Map; + + function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { + if (!outputFingerprints) { + outputFingerprints = createMap(); + } + + const hash = host.createHash(data); + const mtimeBefore = host.getModifiedTime(fileName); + + if (mtimeBefore) { + const fingerprint = outputFingerprints.get(fileName); + // If output has not been changed, and the file has no external modification + if (fingerprint && + fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + return; } + } + + host.writeFile(fileName, data, writeByteOrderMark); + + const mtimeAfter = host.getModifiedTime(fileName); + + outputFingerprints.set(fileName, { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter }); } - cachedProgram = program; - } - // If a source file changes, mark it as unwatched and start the recompilation timer - function sourceFileChanged(sourceFile: SourceFile, eventKind: FileWatcherEventKind) { - sourceFile.fileWatcher.close(); - sourceFile.fileWatcher = undefined; - if (eventKind === FileWatcherEventKind.Deleted) { - unorderedRemoveItem(rootFileNames, sourceFile.fileName); - } - startTimerForRecompilation(); - } + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - // If the configuration file changes, forget cached program and start the recompilation timer - function configFileChanged() { - setCachedProgram(undefined); - cachedConfigFileText = undefined; - startTimerForRecompilation(); - } + //if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { + writeFileIfUpdated(fileName, data, writeByteOrderMark); + //} + //else { + //host.writeFile(fileName, data, writeByteOrderMark); + //} - function watchedDirectoryChanged(fileName: string) { - if (fileName && !ts.isSupportedSourceFileName(fileName, compilerOptions)) { - return; + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); + } + } } - startTimerForHandlingDirectoryChanges(); + const newLine = getNewLineCharacter(options); + const realpath = host.realpath && ((path: string) => host.realpath(path)); + + return { + getSourceFile: getVersionedSourceFile, + getSourceFileByPath: getVersionedSourceFileByPath, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile, + getCurrentDirectory: memoize(() => host.getCurrentDirectory()), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists, + readFile: fileName => host.readFile(fileName), + trace: (s: string) => host.write(s + newLine), + directoryExists: directoryName => host.directoryExists(directoryName), + getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => host.getDirectories(path), + realpath, + onReleaseOldSourceFile + }; + + // TODO: cache module resolution + //if (host.resolveModuleNames) { + // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + //} + //if (host.resolveTypeReferenceDirectives) { + // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { + // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); + // }; + //} } - function startTimerForHandlingDirectoryChanges() { - if (!sys.setTimeout || !sys.clearTimeout) { - return; + function fileExists(fileName: string) { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const hostSourceFileInfo = sourceFilesCache.get(path); + if (hostSourceFileInfo !== undefined) { + return !isString(hostSourceFileInfo); } - if (timerHandleForDirectoryChanges) { - sys.clearTimeout(timerHandleForDirectoryChanges); - } - timerHandleForDirectoryChanges = sys.setTimeout(directoryChangeHandler, 250); + return host.fileExists(fileName); } - function directoryChangeHandler() { - const parsedCommandLine = parseConfigFile(); - const newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName); - const canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(host.getExecutingFilePath())); + } - // We check if the project file list has changed. If so, we just throw away the old program and start fresh. - if (!arrayIsEqualTo(newFileNames && newFileNames.sort(), canonicalRootFileNames && canonicalRootFileNames.sort())) { - setCachedProgram(undefined); - startTimerForRecompilation(); + function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + } + + function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + const hostSourceFile = sourceFilesCache.get(path); + // No source file on the host + if (isString(hostSourceFile)) { + return undefined; + } + + // Create new source file if requested or the versions dont match + if (!hostSourceFile) { + const sourceFile = getSourceFile(fileName, languageVersion, onError); + if (sourceFile) { + sourceFile.version = "0"; + const fileWatcher = watchSourceFileForChanges(sourceFile.path); + sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + } + else { + sourceFilesCache.set(path, "0"); + } + return sourceFile; + } + else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { + if (shouldCreateNewSourceFile) { + hostSourceFile.version++; + } + const newSourceFile = getSourceFile(fileName, languageVersion, onError); + if (newSourceFile) { + newSourceFile.version = hostSourceFile.version.toString(); + hostSourceFile.sourceFile = newSourceFile; + } + else { + // File doesnt exist any more + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, hostSourceFile.version.toString()); + } + + return newSourceFile; + } + + return hostSourceFile.sourceFile; + + function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { + let text: string; + try { + performance.mark("beforeIORead"); + text = host.readFile(fileName, compilerOptions.charset); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + text = ""; + } + + return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; + } + } + + function removeSourceFile(path: Path) { + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile !== undefined) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + } + sourceFilesCache.delete(path); + } + } + + function getSourceVersion(path: Path): string { + const hostSourceFile = sourceFilesCache.get(path); + return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); + } + + function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { + const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); + // If this is the source file thats in the cache and new program doesnt need it, + // remove the cached entry. + // Note we arent deleting entry if file became missing in new program or + // there was version update and new source file was created. + if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { + sourceFilesCache.delete(oldSourceFile.path); } } // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. - function startTimerForRecompilation() { + function scheduleProgramUpdate() { if (!sys.setTimeout || !sys.clearTimeout) { return; } - if (timerHandleForRecompilation) { - sys.clearTimeout(timerHandleForRecompilation); + if (timerToUpdateProgram) { + sys.clearTimeout(timerToUpdateProgram); } - timerHandleForRecompilation = sys.setTimeout(recompile, 250); + timerToUpdateProgram = sys.setTimeout(updateProgram, 250); } - function recompile() { - timerHandleForRecompilation = undefined; + function scheduleProgramReload() { + Debug.assert(!!configFileName); + needsReload = true; + scheduleProgramUpdate(); + } + + function updateProgram() { + timerToUpdateProgram = undefined; reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - performCompilation(); + + if (needsReload) { + reloadConfigFile(); + } + else { + synchronizeProgram(); + } + } + + function reloadConfigFile() { + writeLog(`Reloading config file: ${configFileName}`); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + + needsReload = false; + + const cachedHost = host as CachedSystem; + cachedHost.clearCache(); + const configParseResult = parseConfigFile(configFileName, commandLine, cachedHost); + rootFileNames = configParseResult.fileNames; + compilerOptions = configParseResult.options; + configFileSpecs = configParseResult.configFileSpecs; + configFileWildCardDirectories = configParseResult.wildcardDirectories; + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + } + + function watchSourceFileForChanges(path: Path) { + return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); + } + + function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { + writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile) { + // Update the cache + if (eventKind === FileWatcherEventKind.Deleted) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, (hostSourceFile.version++).toString()); + } + } + else { + // Deleted file created + if (isString(hostSourceFile)) { + sourceFilesCache.delete(path); + } + else { + // file changed - just update the version + hostSourceFile.version++; + } + } + } + // Update the program + scheduleProgramUpdate(); + } + + function watchMissingFilePath(missingFilePath: Path) { + return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); + } + + function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { + fileWatcher.close(); + } + + function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { + writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); + if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { + closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); + missingFilesMap.delete(missingFilePath); + + if (configFileName) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); + } + + // Delete the entry in the source files cache so that new source file is created + removeSourceFile(missingFilePath); + + // When a missing file is created, we should update the graph. + scheduleProgramUpdate(); + } + } + + function watchConfigFileWildCardDirectories() { + const wildcards = createMapFromTemplate(configFileWildCardDirectories); + watchedWildCardDirectories = updateWatchingWildcardDirectories( + watchedWildCardDirectories, wildcards, + watchWildCardDirectory, stopWatchingWildCardDirectory + ); + } + + function watchWildCardDirectory(directory: string, recursive: boolean) { + return host.watchDirectory(directory, fileName => + onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), + recursive); + } + + function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) { + fileWatcher.close(); + } + + function onFileAddOrRemoveInWatchedDirectory(fileName: string) { + Debug.assert(!!configFileName); + (host as CachedSystem).addOrDeleteFileOrFolder(fileName); + + // Since the file existance changed, update the sourceFiles cache + removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName)); + + // If a change was made inside "folder/file", node will trigger the callback twice: + // one with the fileName being "folder/file", and the other one with "folder". + // We don't respond to the second one. + if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); + return; + } + + writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); + + // Reload is pending, do the reload + if (!needsReload) { + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []); + if (!configFileSpecs.filesSpecs) { + reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined); + } + rootFileNames = result.fileNames; + + // Schedule Update the program + scheduleProgramUpdate(); + } + } + + function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { + writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + scheduleProgramReload(); + } + + function writeLog(s: string) { + const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + if (hasDiagnostics) { + host.write(s); + } } } - function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { + interface CachedSystem extends System { + addOrDeleteFileOrFolder(fileOrFolder: string): void; + clearCache(): void; + } + + function createCachedSystem(host: System): CachedSystem { + const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; + const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; + const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; + const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; + const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; + const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; + const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; + const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; + const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; + const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; + + const cachedHost = createCachedHost(host); + return { + args: host.args, + newLine: host.newLine, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, + write: s => host.write(s), + readFile: (path, encoding?) => host.readFile(path, encoding), + getFileSize, + writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), + watchFile, + watchDirectory, + resolvePath: path => host.resolvePath(path), + fileExists: fileName => cachedHost.fileExists(fileName), + directoryExists: dir => cachedHost.directoryExists(dir), + createDirectory: dir => cachedHost.createDirectory(dir), + getExecutingFilePath: () => host.getExecutingFilePath(), + getCurrentDirectory: () => cachedHost.getCurrentDirectory(), + getDirectories: dir => cachedHost.getDirectories(dir), + readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), + getModifiedTime, + createHash, + getMemoryUsage, + exit: exitCode => host.exit(exitCode), + realpath, + getEnvironmentVariable: name => host.getEnvironmentVariable(name), + tryEnableSourceMapsForHost, + debugMode: host.debugMode, + setTimeout, + clearTimeout, + addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), + clearCache: () => cachedHost.clearCache() + }; + } + + function parseConfigFile(configFileName: string, commandLine: ParsedCommandLine, host: System): ParsedCommandLine { + let configFileText: string; + try { + configFileText = host.readFile(configFileName); + } + catch (e) { + const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); + reportWatchDiagnostic(error); + host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + if (!configFileText) { + const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); + reportDiagnostics([error], /* compilerHost */ undefined); + host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + + const result = parseJsonText(configFileName, configFileText); + reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + + const cwd = host.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); + reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); + + return configParseResult; + } + + function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) { const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; let statistics: Statistic[]; if (hasDiagnostics) { @@ -416,7 +741,7 @@ namespace ts { statistics = []; } - const program = createProgram(fileNames, compilerOptions, compilerHost); + const program = createProgram(fileNames, compilerOptions, compilerHost, oldProgram); const exitStatus = compileProgram(); if (compilerOptions.listFiles) { @@ -481,6 +806,8 @@ namespace ts { } } + // TODO: in watch mode to emit only affected files + // Otherwise, emit and report any errors we ran into. const emitOutput = program.emit(); diagnostics = diagnostics.concat(emitOutput.diagnostics); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8929b19bfe3..45f5a16a80c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2326,6 +2326,7 @@ namespace ts { /* @internal */ patternAmbientModules?: PatternAmbientModule[]; /* @internal */ ambientModuleNames: ReadonlyArray; /* @internal */ checkJsDirective: CheckJsDirective | undefined; + /* @internal */ version: string; } export interface Bundle extends Node { @@ -3952,8 +3953,8 @@ namespace ts { } export interface CompilerHost extends ModuleResolutionHost { - getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; - getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; + getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile; + getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile; getCancellationToken?(): CancellationToken; getDefaultLibFileName(options: CompilerOptions): string; getDefaultLibLocation?(): string; @@ -3977,6 +3978,7 @@ namespace ts { */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string; + onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index facb0bdc2ee..08816382ecf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3611,6 +3611,99 @@ namespace ts { export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; } + + export function compareDataObjects(dst: any, src: any): boolean { + if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) { + return false; + } + + for (const e in dst) { + if (typeof dst[e] === "object") { + if (!compareDataObjects(dst[e], src[e])) { + return false; + } + } + else if (typeof dst[e] !== "function") { + if (dst[e] !== src[e]) { + return false; + } + } + } + return true; + } + + export function cleanExistingMap( + existingMap: Map, + onDeleteExistingValue: (key: string, existingValue: T) => void) { + if (existingMap) { + // Remove all + existingMap.forEach((existingValue, key) => { + existingMap.delete(key); + onDeleteExistingValue(key, existingValue); + }); + } + } + + export function mutateExistingMapWithNewSet( + existingMap: Map, newMap: Map, + createNewValue: (key: string) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void + ): Map { + return mutateExistingMap( + existingMap, newMap, + /*createNewValue*/(key, _valueInNewMap) => createNewValue(key), + onDeleteExistingValue, + ); + } + + export function mutateExistingMap( + existingMap: Map, newMap: Map, + createNewValue: (key: string, valueInNewMap: U) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void, + isSameValue?: (existingValue: T, valueInNewMap: U) => boolean, + OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void, + onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void + ): Map { + // If there are new values update them + if (newMap) { + if (existingMap) { + // Needs update + existingMap.forEach((existingValue, key) => { + const valueInNewMap = newMap.get(key); + // Existing value - remove it + if (valueInNewMap === undefined) { + existingMap.delete(key); + onDeleteExistingValue(key, existingValue); + } + // different value - remove it + else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) { + existingMap.delete(key); + OnDeleteExistingMismatchValue(key, existingValue); + } + else if (onSameExistingValue) { + onSameExistingValue(existingValue, valueInNewMap); + } + }); + } + else { + // Create new + existingMap = createMap(); + } + + // Add new values that are not already present + newMap.forEach((valueInNewMap, key) => { + if (!existingMap.has(key)) { + // New values + existingMap.set(key, createNewValue(key, valueInNewMap)); + } + }); + + return existingMap; + } + + cleanExistingMap(existingMap, onDeleteExistingValue); + return undefined; + } } namespace ts { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0b5cb8c119a..d617bdb2b9e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1350,7 +1350,7 @@ namespace ts.server { } private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { - const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName); + const cachedServerHost = new CachedServerHost(this.host); const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); this.logger.info(`Opened configuration file ${configFileName}`); return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index fb1cd80b660..692547e4915 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -8,13 +8,12 @@ namespace ts.server { newLine: string; useCaseSensitiveFileNames: boolean; + private readonly cachedHost: CachedHost; + readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; - private cachedReadDirectoryResult = createMap(); - private readonly currentDirectory: string; - - constructor(private readonly host: ServerHost, private getCanonicalFileName: (fileName: string) => string) { + constructor(private readonly host: ServerHost) { this.args = host.args; this.newLine = host.newLine; this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; @@ -24,41 +23,7 @@ namespace ts.server { if (this.host.realpath) { this.realpath = path => this.host.realpath(path); } - this.currentDirectory = this.host.getCurrentDirectory(); - } - - private toPath(fileName: string) { - return toPath(fileName, this.currentDirectory, this.getCanonicalFileName); - } - - private getFileSystemEntries(rootDir: string) { - const path = this.toPath(rootDir); - const cachedResult = this.cachedReadDirectoryResult.get(path); - if (cachedResult) { - return cachedResult; - } - - const resultFromHost: FileSystemEntries = { - files: this.host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], - directories: this.host.getDirectories(rootDir) || [] - }; - - this.cachedReadDirectoryResult.set(path, resultFromHost); - return resultFromHost; - } - - private canWorkWithCacheForDir(rootDir: string) { - // Some of the hosts might not be able to handle read directory or getDirectories - const path = this.toPath(rootDir); - if (this.cachedReadDirectoryResult.get(path)) { - return true; - } - try { - return this.getFileSystemEntries(rootDir); - } - catch (_e) { - return false; - } + this.cachedHost = createCachedHost(host); } write(s: string) { @@ -66,13 +31,7 @@ namespace ts.server { } writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - const path = this.toPath(fileName); - const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); - const baseFileName = getBaseFileName(toNormalizedPath(fileName)); - if (result) { - result.files = this.updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true); - } - return this.host.writeFile(fileName, data, writeByteOrderMark); + this.cachedHost.writeFile(fileName, data, writeByteOrderMark); } resolvePath(path: string) { @@ -88,7 +47,7 @@ namespace ts.server { } getCurrentDirectory() { - return this.currentDirectory; + return this.cachedHost.getCurrentDirectory(); } exit(exitCode?: number) { @@ -101,78 +60,32 @@ namespace ts.server { } getDirectories(rootDir: string) { - if (this.canWorkWithCacheForDir(rootDir)) { - return this.getFileSystemEntries(rootDir).directories.slice(); - } - return this.host.getDirectories(rootDir); + return this.cachedHost.getDirectories(rootDir); } readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - if (this.canWorkWithCacheForDir(rootDir)) { - return matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, depth, path => this.getFileSystemEntries(path)); - } - return this.host.readDirectory(rootDir, extensions, excludes, includes, depth); + return this.cachedHost.readDirectory(rootDir, extensions, excludes, includes, depth); } fileExists(fileName: string): boolean { - const path = this.toPath(fileName); - const result = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); - const baseName = getBaseFileName(toNormalizedPath(fileName)); - return (result && this.hasEntry(result.files, baseName)) || this.host.fileExists(fileName); + return this.cachedHost.fileExists(fileName); } directoryExists(dirPath: string) { - const path = this.toPath(dirPath); - return this.cachedReadDirectoryResult.has(path) || this.host.directoryExists(dirPath); + return this.cachedHost.directoryExists(dirPath); } readFile(path: string, encoding?: string): string { return this.host.readFile(path, encoding); } - private fileNameEqual(name1: string, name2: string) { - return this.getCanonicalFileName(name1) === this.getCanonicalFileName(name2); - } - - private hasEntry(entries: ReadonlyArray, name: string) { - return some(entries, file => this.fileNameEqual(file, name)); - } - - private updateFileSystemEntry(entries: ReadonlyArray, baseName: string, isValid: boolean) { - if (this.hasEntry(entries, baseName)) { - if (!isValid) { - return filter(entries, entry => !this.fileNameEqual(entry, baseName)); - } - } - else if (isValid) { - return entries.concat(baseName); - } - return entries; - } addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) { - const path = this.toPath(fileOrFolder); - const existingResult = this.cachedReadDirectoryResult.get(path); - if (existingResult) { - if (!this.host.directoryExists(fileOrFolder)) { - this.cachedReadDirectoryResult.delete(path); - } - } - else { - // Was this earlier file - const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); - if (parentResult) { - const baseName = getBaseFileName(fileOrFolder); - if (parentResult) { - parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); - parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); - } - } - } + return this.cachedHost.addOrDeleteFileOrFolder(fileOrFolder); } clearCache() { - this.cachedReadDirectoryResult = createMap(); + return this.cachedHost.clearCache(); } setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { diff --git a/src/server/project.ts b/src/server/project.ts index 483dfe841a7..d7c062178c4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -639,38 +639,13 @@ namespace ts.server { } } - const missingFilePaths = this.program.getMissingFilePaths(); - const newMissingFilePathMap = arrayToSet(missingFilePaths); // Update the missing file paths watcher - this.missingFilesMap = mutateExistingMapWithNewSet( - this.missingFilesMap, newMissingFilePathMap, + this.missingFilesMap = updateMissingFilePathsWatch(this.program, this.missingFilesMap, // Watch the missing files - missingFilePath => { - const fileWatcher = this.projectService.addFileWatcher( - WatchType.MissingFilePath, this, missingFilePath, - (filename, eventKind) => { - if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { - this.missingFilesMap.delete(missingFilePath); - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); - - if (this.projectKind === ProjectKind.Configured) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); - } - - // When a missing file is created, we should update the graph. - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); - } - } - ); - return fileWatcher; - }, + missingFilePath => this.addMissingFileWatcher(missingFilePath), // Files that are no longer missing (e.g. because they are no longer required) // should no longer be watched. - (missingFilePath, fileWatcher) => { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded); - } + (missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded) ); } @@ -694,6 +669,32 @@ namespace ts.server { return hasChanges; } + private addMissingFileWatcher(missingFilePath: Path) { + const fileWatcher = this.projectService.addFileWatcher( + WatchType.MissingFilePath, this, missingFilePath, + (filename, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { + this.missingFilesMap.delete(missingFilePath); + this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); + + if (this.projectKind === ProjectKind.Configured) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + } + + // When a missing file is created, we should update the graph. + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + } + } + ); + return fileWatcher; + } + + private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) { + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason); + } + isWatchedMissingFile(path: Path) { return this.missingFilesMap && this.missingFilesMap.has(path); } @@ -1135,33 +1136,18 @@ namespace ts.server { } watchWildcards(wildcardDirectories: Map) { - this.directoriesWatchedForWildcards = mutateExistingMap( - this.directoriesWatchedForWildcards, wildcardDirectories, - // Create new watch and recursive info - (directory, flag) => { - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - return { - watcher: this.projectService.addDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - recursive - ), - recursive - }; - }, - // Close existing watch thats not needed any more - (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded + this.directoriesWatchedForWildcards = updateWatchingWildcardDirectories(this.directoriesWatchedForWildcards, + wildcardDirectories, + // Create new directory watcher + (directory, recursive) => this.projectService.addDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), + recursive ), - // Watcher is same if the recursive flags match - ({ recursive: existingRecursive }, flag) => { - // If the recursive dont match, it needs update - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - return existingRecursive !== recursive; - }, - // Close existing watch that doesnt match in recursive flag - (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged + // Close directory watcher + (directory, watcher, recursive, recursiveChanged) => this.projectService.closeDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, watcher, recursive, + recursiveChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded ) ); } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 36b769182e5..85bba441e51 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -292,77 +292,4 @@ namespace ts.server { deleted(oldItems[oldIndex++]); } } - - export function cleanExistingMap( - existingMap: Map, - onDeleteExistingValue: (key: string, existingValue: T) => void) { - if (existingMap) { - // Remove all - existingMap.forEach((existingValue, key) => { - existingMap.delete(key); - onDeleteExistingValue(key, existingValue); - }); - } - } - - export function mutateExistingMapWithNewSet( - existingMap: Map, newMap: Map, - createNewValue: (key: string) => T, - onDeleteExistingValue: (key: string, existingValue: T) => void - ): Map { - return mutateExistingMap( - existingMap, newMap, - /*createNewValue*/(key, _valueInNewMap) => createNewValue(key), - onDeleteExistingValue, - ); - } - - export function mutateExistingMap( - existingMap: Map, newMap: Map, - createNewValue: (key: string, valueInNewMap: U) => T, - onDeleteExistingValue: (key: string, existingValue: T) => void, - isSameValue?: (existingValue: T, valueInNewMap: U) => boolean, - OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void, - onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void - ): Map { - // If there are new values update them - if (newMap) { - if (existingMap) { - // Needs update - existingMap.forEach((existingValue, key) => { - const valueInNewMap = newMap.get(key); - // Existing value - remove it - if (valueInNewMap === undefined) { - existingMap.delete(key); - onDeleteExistingValue(key, existingValue); - } - // different value - remove it - else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) { - existingMap.delete(key); - OnDeleteExistingMismatchValue(key, existingValue); - } - else if (onSameExistingValue) { - onSameExistingValue(existingValue, valueInNewMap); - } - }); - } - else { - // Create new - existingMap = createMap(); - } - - // Add new values that are not already present - newMap.forEach((valueInNewMap, key) => { - if (!existingMap.has(key)) { - // New values - existingMap.set(key, createNewValue(key, valueInNewMap)); - } - }); - - return existingMap; - } - - cleanExistingMap(existingMap, onDeleteExistingValue); - return undefined; - } } diff --git a/src/services/services.ts b/src/services/services.ts index fb87c0065d5..e22d6041159 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1114,9 +1114,9 @@ namespace ts { // Get a fresh cache of the host information let hostCache = new HostCache(host, getCanonicalFileName); - + const rootFileNames = hostCache.getRootFileNames(); // If the program is already up-to-date, we can reuse it - if (programUpToDate()) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), (path) => hostCache.getVersion(path))) { return; } @@ -1126,18 +1126,7 @@ namespace ts { // the program points to old source files that have been invalidated because of // incremental parsing. - const oldSettings = program && program.getCompilerOptions(); const newSettings = hostCache.compilationSettings(); - const shouldCreateNewSourceFiles = oldSettings && - (oldSettings.target !== newSettings.target || - oldSettings.module !== newSettings.module || - oldSettings.moduleResolution !== newSettings.moduleResolution || - oldSettings.noResolve !== newSettings.noResolve || - oldSettings.jsx !== newSettings.jsx || - oldSettings.allowJs !== newSettings.allowJs || - oldSettings.disableSizeLimit !== oldSettings.disableSizeLimit || - oldSettings.baseUrl !== newSettings.baseUrl || - !equalOwnProperties(oldSettings.paths, newSettings.paths)); // Now create a new compiler const compilerHost: CompilerHost = { @@ -1172,7 +1161,8 @@ namespace ts { }, getDirectories: path => { return host.getDirectories ? host.getDirectories(path) : []; - } + }, + onReleaseOldSourceFile }; if (host.trace) { compilerHost.trace = message => host.trace(message); @@ -1188,36 +1178,29 @@ namespace ts { } const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); - const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); - - // Release any files we have acquired in the old program but are - // not part of the new program. - if (program) { - const oldSourceFiles = program.getSourceFiles(); - const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldSettings); - for (const oldSourceFile of oldSourceFiles) { - if (!newProgram.getSourceFile(oldSourceFile.fileName) || shouldCreateNewSourceFiles) { - documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey); - } - } - } + program = createProgram(rootFileNames, newSettings, compilerHost, program); // hostCache is captured in the closure for 'getOrCreateSourceFile' but it should not be used past this point. // It needs to be cleared to allow all collected snapshots to be released hostCache = undefined; - program = newProgram; - // Make sure all the nodes in the program are both bound, and have their parent // pointers set property. program.getTypeChecker(); return; - function getOrCreateSourceFile(fileName: string): SourceFile { - return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName)); + // Release any files we have acquired in the old program but are + // not part of the new program. + function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { + const oldSettingsKey = documentRegistry.getKeyForCompilationSettings(oldOptions); + documentRegistry.releaseDocumentWithKey(oldSourceFile.path, oldSettingsKey); } - function getOrCreateSourceFileByPath(fileName: string, path: Path): SourceFile { + function getOrCreateSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + return getOrCreateSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + } + + function getOrCreateSourceFileByPath(fileName: string, path: Path, _languageVersion: ScriptTarget, _onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { Debug.assert(hostCache !== undefined); // The program is asking for this file, check first if the host can locate it. // If the host can not locate the file, then it does not exist. return undefined @@ -1230,7 +1213,7 @@ namespace ts { // Check if the language version has changed since we last created a program; if they are the same, // it is safe to reuse the sourceFiles; if not, then the shape of the AST can change, and the oldSourceFile // can not be reused. we have to dump all syntax trees and create new ones. - if (!shouldCreateNewSourceFiles) { + if (!shouldCreateNewSourceFile) { // Check if the old program had this file already const oldSourceFile = program && program.getSourceFileByPath(path); if (oldSourceFile) { @@ -1270,49 +1253,6 @@ namespace ts { // Could not find this file in the old program, create a new SourceFile for it. return documentRegistry.acquireDocumentWithKey(fileName, path, newSettings, documentRegistryBucketKey, hostFileInformation.scriptSnapshot, hostFileInformation.version, hostFileInformation.scriptKind); } - - function sourceFileUpToDate(sourceFile: SourceFile): boolean { - if (!sourceFile) { - return false; - } - const path = sourceFile.path || toPath(sourceFile.fileName, currentDirectory, getCanonicalFileName); - return sourceFile.version === hostCache.getVersion(path); - } - - function programUpToDate(): boolean { - // If we haven't create a program yet, then it is not up-to-date - if (!program) { - return false; - } - - // If number of files in the program do not match, it is not up-to-date - const rootFileNames = hostCache.getRootFileNames(); - if (program.getSourceFiles().length !== rootFileNames.length) { - return false; - } - - // If any file is not up-to-date, then the whole program is not up-to-date - for (const fileName of rootFileNames) { - if (!sourceFileUpToDate(program.getSourceFile(fileName))) { - return false; - } - } - - const currentOptions = program.getCompilerOptions(); - const newOptions = hostCache.compilationSettings(); - // If the compilation settings do no match, then the program is not up-to-date - if (!compareDataObjects(currentOptions, newOptions)) { - return false; - } - - // If everything matches but the text of config file is changed, - // error locations can change for program options, so update the program - if (currentOptions.configFile && newOptions.configFile) { - return currentOptions.configFile.text === newOptions.configFile.text; - } - - return true; - } } function getProgram(): Program { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a5b035154be..05d2ebf477c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -994,26 +994,6 @@ namespace ts { return result; } - export function compareDataObjects(dst: any, src: any): boolean { - if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) { - return false; - } - - for (const e in dst) { - if (typeof dst[e] === "object") { - if (!compareDataObjects(dst[e], src[e])) { - return false; - } - } - else if (typeof dst[e] !== "function") { - if (dst[e] !== src[e]) { - return false; - } - } - } - return true; - } - export function isArrayLiteralOrObjectLiteralDestructuringPattern(node: Node) { if (node.kind === SyntaxKind.ArrayLiteralExpression || node.kind === SyntaxKind.ObjectLiteralExpression) { From e06847503c547340b879fadfeef5e7f8cb682b22 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 24 Jul 2017 16:57:49 -0700 Subject: [PATCH 034/144] Refactor so that builder handles only source files and program --- src/server/builder.ts | 217 ++++++++++++++++++++++++------------------ src/server/project.ts | 63 +++++++----- src/server/session.ts | 2 +- 3 files changed, 164 insertions(+), 118 deletions(-) diff --git a/src/server/builder.ts b/src/server/builder.ts index 285ab9ece60..001bd708b2e 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -3,48 +3,45 @@ /// namespace ts.server { - export function shouldEmitFile(scriptInfo: ScriptInfo) { - return !scriptInfo.hasMixedContent; - } - export interface Builder { /** * This is the callback when file infos in the builder are updated */ - onProjectUpdateGraph(): void; - getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; - /** - * @returns {boolean} whether the emit was conducted or not - */ - emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean; + onProgramUpdateGraph(program: Program): void; + getFilesAffectedBy(program: Program, path: Path): string[]; + emitFile(program: Program, path: Path): EmitOutput; clear(): void; } interface EmitHandler { - addScriptInfo(scriptInfo: ScriptInfo): void; + addScriptInfo(program: Program, sourceFile: SourceFile): void; removeScriptInfo(path: Path): void; - updateScriptInfo(scriptInfo: ScriptInfo): void; + updateScriptInfo(program: Program, sourceFile: SourceFile): void; /** * Gets the files affected by the script info which has updated shape from the known one */ - getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[]; + getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[]; } - export function createBuilder(project: Project): Builder { + export function createBuilder( + getCanonicalFileName: (fileName: string) => string, + getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => EmitOutput, + computeHash: (data: string) => string, + shouldEmitFile: (sourceFile: SourceFile) => boolean + ): Builder { let isModuleEmit: boolean | undefined; - let projectVersionForDependencyGraph: string; // Last checked shape signature for the file info let fileInfos: Map; let emitHandler: EmitHandler; return { - onProjectUpdateGraph, + onProgramUpdateGraph, getFilesAffectedBy, emitFile, clear }; - function createProjectGraph() { - const currentIsModuleEmit = project.getCompilerOptions().module !== ModuleKind.None; + function createProgramGraph(program: Program) { + const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None; if (isModuleEmit !== currentIsModuleEmit) { isModuleEmit = currentIsModuleEmit; emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); @@ -52,77 +49,55 @@ namespace ts.server { } fileInfos = mutateExistingMap( - fileInfos, arrayToMap(project.getScriptInfos(), info => info.path), - (_path, info) => { - emitHandler.addScriptInfo(info); + fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), + (_path, sourceFile) => { + emitHandler.addScriptInfo(program, sourceFile); return ""; }, (path: Path, _value) => emitHandler.removeScriptInfo(path), /*isSameValue*/ undefined, /*OnDeleteExistingMismatchValue*/ undefined, - (_prevValue, scriptInfo) => emitHandler.updateScriptInfo(scriptInfo) + (_prevValue, sourceFile) => emitHandler.updateScriptInfo(program, sourceFile) ); - projectVersionForDependencyGraph = project.getProjectVersion(); } - function ensureFileInfos() { + function ensureProgramGraph(program: Program) { if (!emitHandler) { - createProjectGraph(); + createProgramGraph(program); } - Debug.assert(projectVersionForDependencyGraph === project.getProjectVersion()); } - function onProjectUpdateGraph() { + function onProgramUpdateGraph(program: Program) { if (emitHandler) { - createProjectGraph(); + createProgramGraph(program); } } - function getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - ensureFileInfos(); + function getFilesAffectedBy(program: Program, path: Path): string[] { + ensureProgramGraph(program); - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; - const path = scriptInfo.path; - if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(scriptInfo)) { + const sourceFile = program.getSourceFile(path); + const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : []; + if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(program, sourceFile)) { return singleFileResult; } - return emitHandler.getFilesAffectedByUpdatedShape(scriptInfo, singleFileResult); + return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult); } - /** - * @returns {boolean} whether the emit was conducted or not - */ - function emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean { - ensureFileInfos(); - if (!fileInfos || !fileInfos.has(scriptInfo.path)) { - return false; + function emitFile(program: Program, path: Path): EmitOutput { + ensureProgramGraph(program); + if (!fileInfos || !fileInfos.has(path)) { + return { outputFiles: [], emitSkipped: true }; } - const { emitSkipped, outputFiles } = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ false); - if (!emitSkipped) { - const projectRootPath = project.getProjectRootPath(); - for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); - writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); - } - } - return !emitSkipped; + return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false); } function clear() { isModuleEmit = undefined; emitHandler = undefined; fileInfos = undefined; - projectVersionForDependencyGraph = undefined; - } - - function getSourceFile(path: Path) { - return project.getSourceFile(path); - } - - function getScriptInfo(path: Path) { - return project.projectService.getScriptInfoForPath(path); } function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) { @@ -147,12 +122,8 @@ namespace ts.server { /** * @return {boolean} indicates if the shape signature has changed since last update. */ - function updateShapeSignature(scriptInfo: ScriptInfo) { - const path = scriptInfo.path; - const sourceFile = getSourceFile(path); - if (!sourceFile) { - return true; - } + function updateShapeSignature(program: Program, sourceFile: SourceFile) { + const path = sourceFile.path; const prevSignature = fileInfos.get(path); let latestSignature = prevSignature; @@ -161,7 +132,7 @@ namespace ts.server { fileInfos.set(path, latestSignature); } else { - const emitOutput = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ true); + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = computeHash(emitOutput.outputFiles[0].text); fileInfos.set(path, latestSignature); @@ -171,8 +142,67 @@ namespace ts.server { return !prevSignature || latestSignature !== prevSignature; } - function computeHash(text: string) { - return project.projectService.host.createHash(text); + /** + * Gets the referenced files for a file from the program + * @param program + * @param path + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile): Map { + const referencedFiles = createMap(); + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const symbol = checker.getSymbolAtLocation(importName); + if (symbol && symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = symbol.declarations[0].getSourceFile(); + if (declarationSourceFile) { + referencedFiles.set(declarationSourceFile.path, true); + } + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + referencedFiles.set(referencedPath, true); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName; + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); + referencedFiles.set(typeFilePath, true); + }); + } + + return referencedFiles; + } + + /** + * Gets all the emittable files from the program + */ + function getAllEmittableFiles(program: Program) { + const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions()); + const sourceFiles = program.getSourceFiles(); + const result: string[] = []; + for (const sourceFile of sourceFiles) { + if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && shouldEmitFile(sourceFile)) { + result.push(sourceFile.fileName); + } + } + return result; } function noop() { } @@ -185,14 +215,14 @@ namespace ts.server { getFilesAffectedByUpdatedShape }; - function getFilesAffectedByUpdatedShape(_scriptInfo: ScriptInfo, singleFileResult: string[]): string[] { - const options = project.getCompilerOptions(); + function getFilesAffectedByUpdatedShape(program: Program, _sourceFile: SourceFile, singleFileResult: string[]): string[] { + const options = program.getCompilerOptions(); // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, // so returning the file itself is good enough. if (options && (options.out || options.outFile)) { return singleFileResult; } - return project.getAllEmittableFiles(); + return getAllEmittableFiles(program); } } @@ -207,19 +237,22 @@ namespace ts.server { getFilesAffectedByUpdatedShape }; - function setReferences(path: Path, latestVersion: string, existingMap: Map) { - existingMap = mutateExistingMapWithNewSet(existingMap, project.getReferencedFiles(path), + function setReferences(program: Program, sourceFile: SourceFile, existingMap: Map) { + const path = sourceFile.path; + existingMap = mutateExistingMapWithNewSet( + existingMap, + getReferencedFiles(program, sourceFile), // Creating new Reference: Also add referenced by key => { referencedBy.add(key, path); return true; }, // Remove existing reference (key, _existingValue) => { referencedBy.remove(key, path); } ); references.set(path, existingMap); - scriptVersionForReferences.set(path, latestVersion); + scriptVersionForReferences.set(path, sourceFile.version); } - function addScriptInfo(info: ScriptInfo) { - setReferences(info.path, info.getLatestVersion(), undefined); + function addScriptInfo(program: Program, sourceFile: SourceFile) { + setReferences(program, sourceFile, undefined); } function removeScriptInfo(path: Path) { @@ -227,12 +260,11 @@ namespace ts.server { scriptVersionForReferences.delete(path); } - function updateScriptInfo(scriptInfo: ScriptInfo) { - const path = scriptInfo.path; + function updateScriptInfo(program: Program, sourceFile: SourceFile) { + const path = sourceFile.path; const lastUpdatedVersion = scriptVersionForReferences.get(path); - const latestVersion = scriptInfo.getLatestVersion(); - if (lastUpdatedVersion !== latestVersion) { - setReferences(path, latestVersion, references.get(path)); + if (lastUpdatedVersion !== sourceFile.version) { + setReferences(program, sourceFile, references.get(path)); } } @@ -240,14 +272,12 @@ namespace ts.server { return referencedBy.get(path) || []; } - function getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[] { - const path = scriptInfo.path; - const sourceFile = getSourceFile(path); + function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) { - return project.getAllEmittableFiles(); + return getAllEmittableFiles(program); } - const options = project.getCompilerOptions(); + const options = program.getCompilerOptions(); if (options && (options.isolatedModules || options.out || options.outFile)) { return singleFileResult; } @@ -256,22 +286,23 @@ namespace ts.server { // Because if so, its own referencedBy files need to be saved as well to make the // emitting result consistent with files on disk. - const fileNamesMap = createMap(); - const setFileName = (path: Path, scriptInfo: ScriptInfo) => { - fileNamesMap.set(path, scriptInfo && shouldEmitFile(scriptInfo) ? scriptInfo.fileName : undefined); + const fileNamesMap = createMap(); + const setFileName = (path: Path, sourceFile: SourceFile) => { + fileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined); }; // Start with the paths this file was referenced by - setFileName(path, scriptInfo); + const path = sourceFile.path; + setFileName(path, sourceFile); const queue = getReferencedByPaths(path).slice(); while (queue.length > 0) { const currentPath = queue.pop(); if (!fileNamesMap.has(currentPath)) { - const currentScriptInfo = getScriptInfo(currentPath); - if (currentScriptInfo && updateShapeSignature(currentScriptInfo)) { + const currentSourceFile = program.getSourceFileByPath(currentPath); + if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { queue.push(...getReferencedByPaths(currentPath)); } - setFileName(currentPath, currentScriptInfo); + setFileName(currentPath, currentSourceFile); } } diff --git a/src/server/project.ts b/src/server/project.ts index d7c062178c4..851a26a5d4a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -219,7 +219,6 @@ namespace ts.server { this.disableLanguageService(); } - this.builder = createBuilder(this); this.markAsDirty(); } @@ -247,12 +246,41 @@ namespace ts.server { return this.languageService; } + private ensureBuilder() { + if (!this.builder) { + this.builder = createBuilder( + this.projectService.toCanonicalFileName, + (_program, sourceFile, emitOnlyDts) => this.getFileEmitOutput(sourceFile, emitOnlyDts), + data => this.projectService.host.createHash(data), + sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent + ); + } + } + getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] { if (!this.languageServiceEnabled) { return []; } this.updateGraph(); - return this.builder.getFilesAffectedBy(scriptInfo); + this.ensureBuilder(); + return this.builder.getFilesAffectedBy(this.program, scriptInfo.path); + } + + /** + * Returns true if emit was conducted + */ + emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean { + this.ensureBuilder(); + const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path); + if (!emitSkipped) { + const projectRootPath = this.getProjectRootPath(); + for (const outputFile of outputFiles) { + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); + writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); + } + } + + return !emitSkipped; } getProjectVersion() { @@ -390,11 +418,11 @@ namespace ts.server { }); } - getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) { + private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean) { if (!this.languageServiceEnabled) { return undefined; } - return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); + return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { @@ -453,21 +481,6 @@ namespace ts.server { return false; } - getAllEmittableFiles() { - if (!this.languageServiceEnabled) { - return []; - } - const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions); - const infos = this.getScriptInfos(); - const result: string[] = []; - for (const info of infos) { - if (getBaseFileName(info.fileName) !== defaultLibraryFileName && shouldEmitFile(info)) { - result.push(info.fileName); - } - } - return result; - } - containsScriptInfo(info: ScriptInfo): boolean { return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined); } @@ -594,11 +607,13 @@ namespace ts.server { // update builder only if language service is enabled // otherwise tell it to drop its internal state - if (this.languageServiceEnabled && this.compileOnSaveEnabled) { - this.builder.onProjectUpdateGraph(); - } - else { - this.builder.clear(); + if (this.builder) { + if (this.languageServiceEnabled && this.compileOnSaveEnabled) { + this.builder.onProgramUpdateGraph(this.program); + } + else { + this.builder.clear(); + } } if (hasChanges) { diff --git a/src/server/session.ts b/src/server/session.ts index 1e851d09615..31cb8c1e15d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1223,7 +1223,7 @@ namespace ts.server { return false; } const scriptInfo = project.getScriptInfo(file); - return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark)); + return project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark)); } private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems { From 6237b221da8dc57c2b17d7a962b94133715aab06 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 24 Jul 2017 16:57:49 -0700 Subject: [PATCH 035/144] Move the builder to compiler directory --- src/{server => compiler}/builder.ts | 33 ++++++++++++++++++++++++----- src/compiler/program.ts | 1 + src/compiler/tsconfig.json | 1 + src/server/project.ts | 2 +- src/services/services.ts | 13 +----------- src/services/types.ts | 11 ---------- 6 files changed, 32 insertions(+), 29 deletions(-) rename src/{server => compiler}/builder.ts (90%) diff --git a/src/server/builder.ts b/src/compiler/builder.ts similarity index 90% rename from src/server/builder.ts rename to src/compiler/builder.ts index 001bd708b2e..b830e517e5c 100644 --- a/src/server/builder.ts +++ b/src/compiler/builder.ts @@ -1,8 +1,17 @@ -/// -/// -/// +/// + +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } -namespace ts.server { export interface Builder { /** * This is the callback when file infos in the builder are updated @@ -13,6 +22,20 @@ namespace ts.server { clear(): void; } + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { + const outputFiles: OutputFile[] = []; + const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + return { + outputFiles, + emitSkipped: emitOutput.emitSkipped + }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + } + } + interface EmitHandler { addScriptInfo(program: Program, sourceFile: SourceFile): void; removeScriptInfo(path: Path): void; @@ -157,7 +180,7 @@ namespace ts.server { for (const importName of sourceFile.imports) { const symbol = checker.getSymbolAtLocation(importName); if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = symbol.declarations[0].getSourceFile(); + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); if (declarationSourceFile) { referencedFiles.set(declarationSourceFile.path, true); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0c319b18053..e2bfa647eb7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,6 +1,7 @@ /// /// /// +/// namespace ts { const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 3709d65b7fd..6c50bef7216 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -36,6 +36,7 @@ "declarationEmitter.ts", "emitter.ts", "program.ts", + "builder.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" diff --git a/src/server/project.ts b/src/server/project.ts index 851a26a5d4a..d764e1701d8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -3,7 +3,7 @@ /// /// /// -/// +/// namespace ts.server { diff --git a/src/services/services.ts b/src/services/services.ts index e22d6041159..091364b5c50 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1471,19 +1471,8 @@ namespace ts { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); - const outputFiles: OutputFile[] = []; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } - const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - - return { - outputFiles, - emitSkipped: emitOutput.emitSkipped - }; + return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers); } // Signature help diff --git a/src/services/types.ts b/src/services/types.ts index 6ff7402f3ad..34d79b311ca 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -693,23 +693,12 @@ namespace ts { autoCollapse: boolean; } - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - export const enum OutputFileType { JavaScript, SourceMap, Declaration } - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - export const enum EndOfLineState { None, InMultiLineCommentTrivia, From 9b18f7b61c68d34bb6f4110cf84b7e86f862bfff Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 24 Jul 2017 16:57:49 -0700 Subject: [PATCH 036/144] Use builder to emit the files from the tsc.js --- src/compiler/builder.ts | 69 ++++++++++---- src/compiler/tsc.ts | 200 +++++++++++++++++++++------------------- src/server/project.ts | 2 +- 3 files changed, 156 insertions(+), 115 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index b830e517e5c..5661270e8f7 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -6,36 +6,28 @@ namespace ts { emitSkipped: boolean; } + export interface EmitOutputDetailed extends EmitOutput { + diagnostics: Diagnostic[]; + sourceMaps: SourceMapData[]; + emittedSourceFiles: SourceFile[]; + } + export interface OutputFile { name: string; writeByteOrderMark: boolean; text: string; } - export interface Builder { + export interface Builder { /** * This is the callback when file infos in the builder are updated */ onProgramUpdateGraph(program: Program): void; getFilesAffectedBy(program: Program, path: Path): string[]; - emitFile(program: Program, path: Path): EmitOutput; + emitFile(program: Program, path: Path): T | EmitOutput; clear(): void; } - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { - const outputFiles: OutputFile[] = []; - const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - return { - outputFiles, - emitSkipped: emitOutput.emitSkipped - }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } - } - interface EmitHandler { addScriptInfo(program: Program, sourceFile: SourceFile): void; removeScriptInfo(path: Path): void; @@ -46,12 +38,49 @@ namespace ts { getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[]; } - export function createBuilder( + export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed { + return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles, + cancellationToken, customTransformers) as EmitOutputDetailed; + } + + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { + return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles, + cancellationToken, customTransformers); + } + + function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed { + const outputFiles: OutputFile[] = []; + let emittedSourceFiles: SourceFile[]; + const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + if (!isDetailed) { + return { outputFiles, emitSkipped: emitResult.emitSkipped }; + } + + return { + outputFiles, + emitSkipped: emitResult.emitSkipped, + diagnostics: emitResult.diagnostics, + sourceMaps: emitResult.sourceMaps, + emittedSourceFiles + }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + if (isDetailed) { + emittedSourceFiles = concatenate(emittedSourceFiles, sourceFiles); + } + } + } + + export function createBuilder( getCanonicalFileName: (fileName: string) => string, - getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => EmitOutput, + getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T, computeHash: (data: string) => string, shouldEmitFile: (sourceFile: SourceFile) => boolean - ): Builder { + ): Builder { let isModuleEmit: boolean | undefined; // Last checked shape signature for the file info let fileInfos: Map; @@ -108,7 +137,7 @@ namespace ts { return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult); } - function emitFile(program: Program, path: Path): EmitOutput { + function emitFile(program: Program, path: Path): T | EmitOutput { ensureProgramGraph(program); if (!fileInfos || !fileInfos.has(path)) { return { outputFiles: [], emitSkipped: true }; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index cf793919a4c..e15a411d30f 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -2,8 +2,9 @@ /// namespace ts { - export interface SourceFile { - fileWatcher?: FileWatcher; + export interface CompilerHost { + /** If this is the emit based on the graph builder, use it to emit */ + emitWithBuilder?(program: Program): EmitResult; } interface Statistic { @@ -215,16 +216,17 @@ namespace ts { function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike) { let program: Program; - let needsReload: boolean; - let missingFilesMap: Map; - let configFileWatcher: FileWatcher; - let watchedWildCardDirectories: Map; - let timerToUpdateProgram: any; + let needsReload: boolean; // true if the config file changed and needs to reload it from the disk + let missingFilesMap: Map; // Map of file watchers for the missing files + let configFileWatcher: FileWatcher; // watcher for the config file + let watchedWildCardDirectories: Map; // map of watchers for the wild card directories in the config file + let timerToUpdateProgram: any; // timer callback to recompile the program let compilerOptions: CompilerOptions; let rootFileNames: string[]; - const sourceFilesCache = createMap(); + const sourceFilesCache = createMap(); // Cache that stores the source file and version info + let changedFilePaths: Path[] = []; let host: System; if (configFileName) { @@ -241,6 +243,9 @@ namespace ts { const currentDirectory = host.getCurrentDirectory(); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + // There is no extra check needed since we can just rely on the program to decide emit + const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true); + if (compilerOptions.pretty) { reportDiagnosticWorker = reportDiagnosticWithColorAndContext; } @@ -268,85 +273,6 @@ namespace ts { } function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const existingDirectories = createMap(); - function directoryExists(directoryPath: string): boolean { - if (existingDirectories.has(directoryPath)) { - return true; - } - if (host.directoryExists(directoryPath)) { - existingDirectories.set(directoryPath, true); - return true; - } - return false; - } - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - type OutputFingerprint = { - hash: string; - byteOrderMark: boolean; - mtime: Date; - }; - let outputFingerprints: Map; - - function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { - if (!outputFingerprints) { - outputFingerprints = createMap(); - } - - const hash = host.createHash(data); - const mtimeBefore = host.getModifiedTime(fileName); - - if (mtimeBefore) { - const fingerprint = outputFingerprints.get(fileName); - // If output has not been changed, and the file has no external modification - if (fingerprint && - fingerprint.byteOrderMark === writeByteOrderMark && - fingerprint.hash === hash && - fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } - } - - host.writeFile(fileName, data, writeByteOrderMark); - - const mtimeAfter = host.getModifiedTime(fileName); - - outputFingerprints.set(fileName, { - hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter - }); - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - //if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { - writeFileIfUpdated(fileName, data, writeByteOrderMark); - //} - //else { - //host.writeFile(fileName, data, writeByteOrderMark); - //} - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - const newLine = getNewLineCharacter(options); const realpath = host.realpath && ((path: string) => host.realpath(path)); @@ -355,7 +281,7 @@ namespace ts { getSourceFileByPath: getVersionedSourceFileByPath, getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile, + writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, getCurrentDirectory: memoize(() => host.getCurrentDirectory()), useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, getCanonicalFileName, @@ -367,7 +293,8 @@ namespace ts { getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", getDirectories: (path: string) => host.getDirectories(path), realpath, - onReleaseOldSourceFile + onReleaseOldSourceFile, + emitWithBuilder }; // TODO: cache module resolution @@ -379,6 +306,81 @@ namespace ts { // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); // }; //} + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, data, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); + } + } + + function emitWithBuilder(program: Program): EmitResult { + builder.onProgramUpdateGraph(program); + const filesPendingToEmit = changedFilePaths; + changedFilePaths = []; + + const seenFiles = createMap(); + + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; + let sourceMaps: SourceMapData[]; + while (filesPendingToEmit.length) { + const filePath = filesPendingToEmit.pop(); + const affectedFiles = builder.getFilesAffectedBy(program, filePath); + for (const file of affectedFiles) { + if (!seenFiles.has(file)) { + seenFiles.set(file, true); + const sourceFile = program.getSourceFile(file); + if (sourceFile) { + writeFiles(builder.emitFile(program, sourceFile.path)); + } + } + } + } + + return { emitSkipped, diagnostics, emittedFiles, sourceMaps }; + + function writeFiles(emitOutput: EmitOutputDetailed) { + if (emitOutput.emitSkipped) { + emitSkipped = true; + } + + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + // If it emitted more than one source files, just mark all those source files as seen + if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.fileName, true); + } + } + for (const outputFile of emitOutput.outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + (diagnostics || (diagnostics = [])).push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } + } + } + } } function fileExists(fileName: string) { @@ -408,6 +410,7 @@ namespace ts { // Create new source file if requested or the versions dont match if (!hostSourceFile) { + changedFilePaths.push(path); const sourceFile = getSourceFile(fileName, languageVersion, onError); if (sourceFile) { sourceFile.version = "0"; @@ -420,6 +423,7 @@ namespace ts { return sourceFile; } else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { + changedFilePaths.push(path); if (shouldCreateNewSourceFile) { hostSourceFile.version++; } @@ -652,6 +656,10 @@ namespace ts { host.write(s); } } + + function computeHash(data: string) { + return sys.createHash ? sys.createHash(data) : data; + } } interface CachedSystem extends System { @@ -806,16 +814,20 @@ namespace ts { } } - // TODO: in watch mode to emit only affected files - - // Otherwise, emit and report any errors we ran into. - const emitOutput = program.emit(); + // Emit and report any errors we ran into. + let emitOutput: EmitResult; + if (compilerHost.emitWithBuilder) { + emitOutput = compilerHost.emitWithBuilder(program); + } + else { + // Emit whole program + emitOutput = program.emit(); + } diagnostics = diagnostics.concat(emitOutput.diagnostics); reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); reportEmittedFiles(emitOutput.emittedFiles); - if (emitOutput.emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; diff --git a/src/server/project.ts b/src/server/project.ts index d764e1701d8..942ff9f71a4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -130,7 +130,7 @@ namespace ts.server { /*@internal*/ lsHost: LSHost; - builder: Builder; + builder: Builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ From 85b9254a60af7c56c8167a56583ba7df3b3cb4ae Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jul 2017 10:59:02 -0700 Subject: [PATCH 037/144] Refactor out the tsc logic into another file so we can use that to test it out --- src/compiler/tsc.ts | 1016 +---------------------------------- src/compiler/tscLib.ts | 1018 ++++++++++++++++++++++++++++++++++++ src/compiler/tsconfig.json | 1 + 3 files changed, 1020 insertions(+), 1015 deletions(-) create mode 100644 src/compiler/tscLib.ts diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index e15a411d30f..ec93b61bbdf 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -1,1017 +1,3 @@ -/// -/// - -namespace ts { - export interface CompilerHost { - /** If this is the emit based on the graph builder, use it to emit */ - emitWithBuilder?(program: Program): EmitResult; - } - - interface Statistic { - name: string; - value: string; - } - - const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = { - getCurrentDirectory: () => sys.getCurrentDirectory(), - getNewLine: () => sys.newLine, - getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) - }; - - let reportDiagnosticWorker = reportDiagnosticSimply; - - function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) { - reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost); - } - - function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void { - for (const diagnostic of diagnostics) { - reportDiagnostic(diagnostic, host); - } - } - - function reportEmittedFiles(files: string[]): void { - if (!files || files.length === 0) { - return; - } - - const currentDir = sys.getCurrentDirectory(); - - for (const file of files) { - const filepath = getNormalizedAbsolutePath(file, currentDir); - - sys.write(`TSFILE: ${filepath}${sys.newLine}`); - } - } - - function countLines(program: Program): number { - let count = 0; - forEach(program.getSourceFiles(), file => { - count += getLineStarts(file).length; - }); - return count; - } - - function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText; - } - - function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { - sys.write(ts.formatDiagnostics([diagnostic], host)); - } - - function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { - sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine); - } - - function reportWatchDiagnostic(diagnostic: Diagnostic) { - let output = new Date().toLocaleTimeString() + " - "; - - if (diagnostic.file) { - const loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); - output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `; - } - - output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`; - - sys.write(output); - } - - function padLeft(s: string, length: number) { - while (s.length < length) { - s = " " + s; - } - return s; - } - - function padRight(s: string, length: number) { - while (s.length < length) { - s = s + " "; - } - - return s; - } - - function isJSONSupported() { - return typeof JSON === "object" && typeof JSON.parse === "function"; - } - - export function executeCommandLine(args: string[]): void { - const commandLine = parseCommandLine(args); - - // Configuration file name (if any) - let configFileName: string; - - if (commandLine.options.locale) { - if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors); - } - - // If there are any errors due to command line parsing and/or - // setting up localization, report them and quit. - if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, /*host*/ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - if (commandLine.options.init) { - writeConfigFile(commandLine.options, commandLine.fileNames); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.version) { - printVersion(); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.help || commandLine.options.all) { - printVersion(); - printHelp(commandLine.options.all); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.project) { - if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - if (commandLine.fileNames.length !== 0) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - const fileOrDirectory = normalizePath(commandLine.options.project); - if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) { - configFileName = combinePaths(fileOrDirectory, "tsconfig.json"); - if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - else { - configFileName = fileOrDirectory; - if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project), /* host */ undefined); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - } - else if (commandLine.fileNames.length === 0 && isJSONSupported()) { - const searchPath = normalizePath(sys.getCurrentDirectory()); - configFileName = findConfigFile(searchPath, sys.fileExists); - } - - if (commandLine.fileNames.length === 0 && !configFileName) { - printVersion(); - printHelp(commandLine.options.all); - return sys.exit(ExitStatus.Success); - } - - if (configFileName) { - const configParseResult = parseConfigFile(configFileName, commandLine, sys); - const { fileNames, options } = configParseResult; - if (isWatchSet(configParseResult.options)) { - reportWatchModeWithoutSysSupport(); - createWatchMode(commandLine, configFileName, fileNames, options, configParseResult.configFileSpecs, configParseResult.wildcardDirectories); - } - else { - performCompilation(fileNames, options); - } - } - else if (isWatchSet(commandLine.options)) { - reportWatchModeWithoutSysSupport(); - createWatchMode(commandLine); - } - else { - performCompilation(commandLine.fileNames, commandLine.options); - } - - function reportWatchModeWithoutSysSupport() { - if (!sys.watchFile || !sys.watchDirectory) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - - function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { - if (compilerOptions.pretty) { - reportDiagnosticWorker = reportDiagnosticWithColorAndContext; - } - - const compilerHost = createCompilerHost(compilerOptions); - const compileResult = compile(rootFileNames, compilerOptions, compilerHost); - return sys.exit(compileResult.exitStatus); - } - } - - interface HostFileInfo { - version: number; - sourceFile: SourceFile; - fileWatcher: FileWatcher; - } - - function createWatchMode(commandLine: ParsedCommandLine, configFileName?: string, configFileRootFiles?: string[], configFileOptions?: CompilerOptions, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike) { - let program: Program; - let needsReload: boolean; // true if the config file changed and needs to reload it from the disk - let missingFilesMap: Map; // Map of file watchers for the missing files - let configFileWatcher: FileWatcher; // watcher for the config file - let watchedWildCardDirectories: Map; // map of watchers for the wild card directories in the config file - let timerToUpdateProgram: any; // timer callback to recompile the program - - let compilerOptions: CompilerOptions; - let rootFileNames: string[]; - - const sourceFilesCache = createMap(); // Cache that stores the source file and version info - let changedFilePaths: Path[] = []; - - let host: System; - if (configFileName) { - rootFileNames = configFileRootFiles; - compilerOptions = configFileOptions; - host = createCachedSystem(sys); - configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged); - } - else { - rootFileNames = commandLine.fileNames; - compilerOptions = commandLine.options; - host = sys; - } - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - - // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true); - - if (compilerOptions.pretty) { - reportDiagnosticWorker = reportDiagnosticWithColorAndContext; - } - - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - - function synchronizeProgram() { - writeLog(`Synchronizing program`); - - if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) { - return; - } - - // Create the compiler host - const compilerHost = createWatchedCompilerHost(compilerOptions); - program = compile(rootFileNames, compilerOptions, compilerHost, program).program; - - // Update watches - missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); - - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); - } - - function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const newLine = getNewLineCharacter(options); - const realpath = host.realpath && ((path: string) => host.realpath(path)); - - return { - getSourceFile: getVersionedSourceFile, - getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, - getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists, - readFile: fileName => host.readFile(fileName), - trace: (s: string) => host.write(s + newLine), - directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => host.getDirectories(path), - realpath, - onReleaseOldSourceFile, - emitWithBuilder - }; - - // TODO: cache module resolution - //if (host.resolveModuleNames) { - // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); - //} - //if (host.resolveTypeReferenceDirectives) { - // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { - // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); - // }; - //} - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - host.writeFile(fileName, data, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); - } - } - - function emitWithBuilder(program: Program): EmitResult { - builder.onProgramUpdateGraph(program); - const filesPendingToEmit = changedFilePaths; - changedFilePaths = []; - - const seenFiles = createMap(); - - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - while (filesPendingToEmit.length) { - const filePath = filesPendingToEmit.pop(); - const affectedFiles = builder.getFilesAffectedBy(program, filePath); - for (const file of affectedFiles) { - if (!seenFiles.has(file)) { - seenFiles.set(file, true); - const sourceFile = program.getSourceFile(file); - if (sourceFile) { - writeFiles(builder.emitFile(program, sourceFile.path)); - } - } - } - } - - return { emitSkipped, diagnostics, emittedFiles, sourceMaps }; - - function writeFiles(emitOutput: EmitOutputDetailed) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - - diagnostics = concatenate(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - // If it emitted more than one source files, just mark all those source files as seen - if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.fileName, true); - } - } - for (const outputFile of emitOutput.outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - (diagnostics || (diagnostics = [])).push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); - } - } - } - } - } - - function fileExists(fileName: string) { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const hostSourceFileInfo = sourceFilesCache.get(path); - if (hostSourceFileInfo !== undefined) { - return !isString(hostSourceFileInfo); - } - - return host.fileExists(fileName); - } - - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(host.getExecutingFilePath())); - } - - function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); - } - - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - const hostSourceFile = sourceFilesCache.get(path); - // No source file on the host - if (isString(hostSourceFile)) { - return undefined; - } - - // Create new source file if requested or the versions dont match - if (!hostSourceFile) { - changedFilePaths.push(path); - const sourceFile = getSourceFile(fileName, languageVersion, onError); - if (sourceFile) { - sourceFile.version = "0"; - const fileWatcher = watchSourceFileForChanges(sourceFile.path); - sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); - } - else { - sourceFilesCache.set(path, "0"); - } - return sourceFile; - } - else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { - changedFilePaths.push(path); - if (shouldCreateNewSourceFile) { - hostSourceFile.version++; - } - const newSourceFile = getSourceFile(fileName, languageVersion, onError); - if (newSourceFile) { - newSourceFile.version = hostSourceFile.version.toString(); - hostSourceFile.sourceFile = newSourceFile; - } - else { - // File doesnt exist any more - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, hostSourceFile.version.toString()); - } - - return newSourceFile; - } - - return hostSourceFile.sourceFile; - - function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { - let text: string; - try { - performance.mark("beforeIORead"); - text = host.readFile(fileName, compilerOptions.charset); - performance.mark("afterIORead"); - performance.measure("I/O Read", "beforeIORead", "afterIORead"); - } - catch (e) { - if (onError) { - onError(e.message); - } - text = ""; - } - - return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; - } - } - - function removeSourceFile(path: Path) { - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile !== undefined) { - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - } - sourceFilesCache.delete(path); - } - } - - function getSourceVersion(path: Path): string { - const hostSourceFile = sourceFilesCache.get(path); - return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); - } - - function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { - const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); - // If this is the source file thats in the cache and new program doesnt need it, - // remove the cached entry. - // Note we arent deleting entry if file became missing in new program or - // there was version update and new source file was created. - if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { - sourceFilesCache.delete(oldSourceFile.path); - } - } - - // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch - // operations (such as saving all modified files in an editor) a chance to complete before we kick - // off a new compilation. - function scheduleProgramUpdate() { - if (!sys.setTimeout || !sys.clearTimeout) { - return; - } - - if (timerToUpdateProgram) { - sys.clearTimeout(timerToUpdateProgram); - } - timerToUpdateProgram = sys.setTimeout(updateProgram, 250); - } - - function scheduleProgramReload() { - Debug.assert(!!configFileName); - needsReload = true; - scheduleProgramUpdate(); - } - - function updateProgram() { - timerToUpdateProgram = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - - if (needsReload) { - reloadConfigFile(); - } - else { - synchronizeProgram(); - } - } - - function reloadConfigFile() { - writeLog(`Reloading config file: ${configFileName}`); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - - needsReload = false; - - const cachedHost = host as CachedSystem; - cachedHost.clearCache(); - const configParseResult = parseConfigFile(configFileName, commandLine, cachedHost); - rootFileNames = configParseResult.fileNames; - compilerOptions = configParseResult.options; - configFileSpecs = configParseResult.configFileSpecs; - configFileWildCardDirectories = configParseResult.wildcardDirectories; - - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - } - - function watchSourceFileForChanges(path: Path) { - return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); - } - - function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile) { - // Update the cache - if (eventKind === FileWatcherEventKind.Deleted) { - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, (hostSourceFile.version++).toString()); - } - } - else { - // Deleted file created - if (isString(hostSourceFile)) { - sourceFilesCache.delete(path); - } - else { - // file changed - just update the version - hostSourceFile.version++; - } - } - } - // Update the program - scheduleProgramUpdate(); - } - - function watchMissingFilePath(missingFilePath: Path) { - return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); - } - - function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { - fileWatcher.close(); - } - - function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { - writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); - if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { - closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); - missingFilesMap.delete(missingFilePath); - - if (configFileName) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); - } - - // Delete the entry in the source files cache so that new source file is created - removeSourceFile(missingFilePath); - - // When a missing file is created, we should update the graph. - scheduleProgramUpdate(); - } - } - - function watchConfigFileWildCardDirectories() { - const wildcards = createMapFromTemplate(configFileWildCardDirectories); - watchedWildCardDirectories = updateWatchingWildcardDirectories( - watchedWildCardDirectories, wildcards, - watchWildCardDirectory, stopWatchingWildCardDirectory - ); - } - - function watchWildCardDirectory(directory: string, recursive: boolean) { - return host.watchDirectory(directory, fileName => - onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), - recursive); - } - - function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) { - fileWatcher.close(); - } - - function onFileAddOrRemoveInWatchedDirectory(fileName: string) { - Debug.assert(!!configFileName); - (host as CachedSystem).addOrDeleteFileOrFolder(fileName); - - // Since the file existance changed, update the sourceFiles cache - removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName)); - - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); - return; - } - - writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); - - // Reload is pending, do the reload - if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []); - if (!configFileSpecs.filesSpecs) { - reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined); - } - rootFileNames = result.fileNames; - - // Schedule Update the program - scheduleProgramUpdate(); - } - } - - function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { - writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - scheduleProgramReload(); - } - - function writeLog(s: string) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - if (hasDiagnostics) { - host.write(s); - } - } - - function computeHash(data: string) { - return sys.createHash ? sys.createHash(data) : data; - } - } - - interface CachedSystem extends System { - addOrDeleteFileOrFolder(fileOrFolder: string): void; - clearCache(): void; - } - - function createCachedSystem(host: System): CachedSystem { - const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; - const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; - const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; - const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; - const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; - const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; - const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; - const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; - const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; - const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; - - const cachedHost = createCachedHost(host); - return { - args: host.args, - newLine: host.newLine, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - write: s => host.write(s), - readFile: (path, encoding?) => host.readFile(path, encoding), - getFileSize, - writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), - watchFile, - watchDirectory, - resolvePath: path => host.resolvePath(path), - fileExists: fileName => cachedHost.fileExists(fileName), - directoryExists: dir => cachedHost.directoryExists(dir), - createDirectory: dir => cachedHost.createDirectory(dir), - getExecutingFilePath: () => host.getExecutingFilePath(), - getCurrentDirectory: () => cachedHost.getCurrentDirectory(), - getDirectories: dir => cachedHost.getDirectories(dir), - readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), - getModifiedTime, - createHash, - getMemoryUsage, - exit: exitCode => host.exit(exitCode), - realpath, - getEnvironmentVariable: name => host.getEnvironmentVariable(name), - tryEnableSourceMapsForHost, - debugMode: host.debugMode, - setTimeout, - clearTimeout, - addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), - clearCache: () => cachedHost.clearCache() - }; - } - - function parseConfigFile(configFileName: string, commandLine: ParsedCommandLine, host: System): ParsedCommandLine { - let configFileText: string; - try { - configFileText = host.readFile(configFileName); - } - catch (e) { - const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); - host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - if (!configFileText) { - const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], /* compilerHost */ undefined); - host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - - const result = parseJsonText(configFileName, configFileText); - reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); - - const cwd = host.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); - - return configParseResult; - } - - function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - let statistics: Statistic[]; - if (hasDiagnostics) { - performance.enable(); - statistics = []; - } - - const program = createProgram(fileNames, compilerOptions, compilerHost, oldProgram); - const exitStatus = compileProgram(); - - if (compilerOptions.listFiles) { - forEach(program.getSourceFiles(), file => { - sys.write(file.fileName + sys.newLine); - }); - } - - if (hasDiagnostics) { - const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; - reportCountStatistic("Files", program.getSourceFiles().length); - reportCountStatistic("Lines", countLines(program)); - reportCountStatistic("Nodes", program.getNodeCount()); - reportCountStatistic("Identifiers", program.getIdentifierCount()); - reportCountStatistic("Symbols", program.getSymbolCount()); - reportCountStatistic("Types", program.getTypeCount()); - - if (memoryUsed >= 0) { - reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); - } - - const programTime = performance.getDuration("Program"); - const bindTime = performance.getDuration("Bind"); - const checkTime = performance.getDuration("Check"); - const emitTime = performance.getDuration("Emit"); - if (compilerOptions.extendedDiagnostics) { - performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); - } - else { - // Individual component times. - // Note: To match the behavior of previous versions of the compiler, the reported parse time includes - // I/O read time and processing time for triple-slash references and module imports, and the reported - // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. - reportTimeStatistic("I/O read", performance.getDuration("I/O Read")); - reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); - reportTimeStatistic("Parse time", programTime); - reportTimeStatistic("Bind time", bindTime); - reportTimeStatistic("Check time", checkTime); - reportTimeStatistic("Emit time", emitTime); - } - reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); - reportStatistics(); - - performance.disable(); - } - - return { program, exitStatus }; - - function compileProgram(): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); - } - } - - // Emit and report any errors we ran into. - let emitOutput: EmitResult; - if (compilerHost.emitWithBuilder) { - emitOutput = compilerHost.emitWithBuilder(program); - } - else { - // Emit whole program - emitOutput = program.emit(); - } - diagnostics = diagnostics.concat(emitOutput.diagnostics); - - reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); - - reportEmittedFiles(emitOutput.emittedFiles); - if (emitOutput.emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; - } - - function reportStatistics() { - let nameSize = 0; - let valueSize = 0; - for (const { name, value } of statistics) { - if (name.length > nameSize) { - nameSize = name.length; - } - - if (value.length > valueSize) { - valueSize = value.length; - } - } - - for (const { name, value } of statistics) { - sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); - } - } - - function reportStatisticalValue(name: string, value: string) { - statistics.push({ name, value }); - } - - function reportCountStatistic(name: string, count: number) { - reportStatisticalValue(name, "" + count); - } - - function reportTimeStatistic(name: string, time: number) { - reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); - } - } - - function printVersion() { - sys.write(getDiagnosticText(Diagnostics.Version_0, ts.version) + sys.newLine); - } - - function printHelp(showAllOptions: boolean) { - const output: string[] = []; - - // We want to align our "syntax" and "examples" commands to a certain margin. - const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; - const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; - let marginLength = Math.max(syntaxLength, examplesLength); - - // Build up the syntactic skeleton. - let syntax = makePadding(marginLength - syntaxLength); - syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; - - output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); - output.push(sys.newLine + sys.newLine); - - // Build up the list of examples. - const padding = makePadding(marginLength); - output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); - output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); - output.push(padding + "tsc @args.txt" + sys.newLine); - output.push(sys.newLine); - - output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); - - // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") - const optsList = showAllOptions ? - optionDeclarations.slice().sort((a, b) => compareValues(a.name.toLowerCase(), b.name.toLowerCase())) : - filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView); - - // We want our descriptions to align at the same column in our output, - // so we keep track of the longest option usage string. - marginLength = 0; - const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. - const descriptionColumn: string[] = []; - - const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind - - for (let i = 0; i < optsList.length; i++) { - const option = optsList[i]; - - // If an option lacks a description, - // it is not officially supported. - if (!option.description) { - continue; - } - - let usageText = " "; - if (option.shortName) { - usageText += "-" + option.shortName; - usageText += getParamType(option); - usageText += ", "; - } - - usageText += "--" + option.name; - usageText += getParamType(option); - - usageColumn.push(usageText); - let description: string; - - if (option.name === "lib") { - description = getDiagnosticText(option.description); - const element = (option).element; - const typeMap = >element.type; - optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); - } - else { - description = getDiagnosticText(option.description); - } - - descriptionColumn.push(description); - - // Set the new margin for the description column if necessary. - marginLength = Math.max(usageText.length, marginLength); - } - - // Special case that can't fit in the loop. - const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; - usageColumn.push(usageText); - descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); - marginLength = Math.max(usageText.length, marginLength); - - // Print out each row, aligning all the descriptions on the same column. - for (let i = 0; i < usageColumn.length; i++) { - const usage = usageColumn[i]; - const description = descriptionColumn[i]; - const kindsList = optionsDescriptionMap.get(description); - output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); - - if (kindsList) { - output.push(makePadding(marginLength + 4)); - for (const kind of kindsList) { - output.push(kind + " "); - } - output.push(sys.newLine); - } - } - - for (const line of output) { - sys.write(line); - } - return; - - function getParamType(option: CommandLineOption) { - if (option.paramType !== undefined) { - return " " + getDiagnosticText(option.paramType); - } - return ""; - } - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - } - - function writeConfigFile(options: CompilerOptions, fileNames: string[]) { - const currentDirectory = sys.getCurrentDirectory(); - const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); - if (sys.fileExists(file)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* host */ undefined); - } - else { - sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* host */ undefined); - } - - return; - } -} - -ts.setStackTraceLimit(); - -if (ts.Debug.isDebugging) { - ts.Debug.enableDebugInfo(); -} - -if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { - ts.sys.tryEnableSourceMapsForHost(); -} +/// ts.executeCommandLine(ts.sys.args); diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts new file mode 100644 index 00000000000..3dba8961674 --- /dev/null +++ b/src/compiler/tscLib.ts @@ -0,0 +1,1018 @@ +/// +/// + +namespace ts { + export interface CompilerHost { + /** If this is the emit based on the graph builder, use it to emit */ + emitWithBuilder?(program: Program): EmitResult; + } + + interface Statistic { + name: string; + value: string; + } + + const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = { + getCurrentDirectory: () => sys.getCurrentDirectory(), + getNewLine: () => sys.newLine, + getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) + }; + + let reportDiagnosticWorker = reportDiagnosticSimply; + + function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) { + reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost); + } + + function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void { + for (const diagnostic of diagnostics) { + reportDiagnostic(diagnostic, host); + } + } + + function reportEmittedFiles(files: string[]): void { + if (!files || files.length === 0) { + return; + } + + const currentDir = sys.getCurrentDirectory(); + + for (const file of files) { + const filepath = getNormalizedAbsolutePath(file, currentDir); + + sys.write(`TSFILE: ${filepath}${sys.newLine}`); + } + } + + function countLines(program: Program): number { + let count = 0; + forEach(program.getSourceFiles(), file => { + count += getLineStarts(file).length; + }); + return count; + } + + function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText; + } + + function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { + sys.write(ts.formatDiagnostics([diagnostic], host)); + } + + function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { + sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine); + } + + function reportWatchDiagnostic(diagnostic: Diagnostic) { + let output = new Date().toLocaleTimeString() + " - "; + + if (diagnostic.file) { + const loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); + output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `; + } + + output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`; + + sys.write(output); + } + + function padLeft(s: string, length: number) { + while (s.length < length) { + s = " " + s; + } + return s; + } + + function padRight(s: string, length: number) { + while (s.length < length) { + s = s + " "; + } + + return s; + } + + function isJSONSupported() { + return typeof JSON === "object" && typeof JSON.parse === "function"; + } + + export function executeCommandLine(args: string[]): void { + const commandLine = parseCommandLine(args); + + // Configuration file name (if any) + let configFileName: string; + if (commandLine.options.locale) { + if (!isJSONSupported()) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* host */ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors); + } + + // If there are any errors due to command line parsing and/or + // setting up localization, report them and quit. + if (commandLine.errors.length > 0) { + reportDiagnostics(commandLine.errors, /*host*/ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + if (commandLine.options.init) { + writeConfigFile(commandLine.options, commandLine.fileNames); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.version) { + printVersion(); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.help || commandLine.options.all) { + printVersion(); + printHelp(commandLine.options.all); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.project) { + if (!isJSONSupported()) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* host */ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + if (commandLine.fileNames.length !== 0) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* host */ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + const fileOrDirectory = normalizePath(commandLine.options.project); + if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) { + configFileName = combinePaths(fileOrDirectory, "tsconfig.json"); + if (!sys.fileExists(configFileName)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + else { + configFileName = fileOrDirectory; + if (!sys.fileExists(configFileName)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project), /* host */ undefined); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + } + else if (commandLine.fileNames.length === 0 && isJSONSupported()) { + const searchPath = normalizePath(sys.getCurrentDirectory()); + configFileName = findConfigFile(searchPath, sys.fileExists); + } + + if (commandLine.fileNames.length === 0 && !configFileName) { + printVersion(); + printHelp(commandLine.options.all); + return sys.exit(ExitStatus.Success); + } + + const commandLineOptions = commandLine.options; + if (configFileName) { + const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys); + if (isWatchSet(configParseResult.options)) { + reportWatchModeWithoutSysSupport(); + createWatchModeWithConfigFile(configParseResult, commandLineOptions); + } + else { + performCompilation(configParseResult.fileNames, configParseResult.options); + } + } + else { + if (isWatchSet(commandLine.options)) { + reportWatchModeWithoutSysSupport(); + createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions); + } + else { + performCompilation(commandLine.fileNames, commandLineOptions); + } + } + + function reportWatchModeWithoutSysSupport() { + if (!sys.watchFile || !sys.watchDirectory) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); + sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { + if (compilerOptions.pretty) { + reportDiagnosticWorker = reportDiagnosticWithColorAndContext; + } + + const compilerHost = createCompilerHost(compilerOptions); + const compileResult = compile(rootFileNames, compilerOptions, compilerHost); + return sys.exit(compileResult.exitStatus); + } + } + + interface HostFileInfo { + version: number; + sourceFile: SourceFile; + fileWatcher: FileWatcher; + } + + /* @internal */ + export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend?: CompilerOptions) { + return createWatchMode(configParseResult.fileNames, configParseResult.options, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); + } + + /* @internal */ + export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions) { + return createWatchMode(rootFileNames, compilerOptions); + } + + function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { + let program: Program; + let needsReload: boolean; // true if the config file changed and needs to reload it from the disk + let missingFilesMap: Map; // Map of file watchers for the missing files + let configFileWatcher: FileWatcher; // watcher for the config file + let watchedWildCardDirectories: Map; // map of watchers for the wild card directories in the config file + let timerToUpdateProgram: any; // timer callback to recompile the program + + const sourceFilesCache = createMap(); // Cache that stores the source file and version info + let changedFilePaths: Path[] = []; + + let host: System; + if (configFileName) { + host = createCachedSystem(sys); + configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged); + } + else { + host = sys; + } + const currentDirectory = host.getCurrentDirectory(); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + + // There is no extra check needed since we can just rely on the program to decide emit + const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true); + + if (compilerOptions.pretty) { + reportDiagnosticWorker = reportDiagnosticWithColorAndContext; + } + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + + function synchronizeProgram() { + writeLog(`Synchronizing program`); + + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) { + return; + } + + // Create the compiler host + const compilerHost = createWatchedCompilerHost(compilerOptions); + program = compile(rootFileNames, compilerOptions, compilerHost, program).program; + + // Update watches + missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); + + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + } + + function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { + const newLine = getNewLineCharacter(options); + const realpath = host.realpath && ((path: string) => host.realpath(path)); + + return { + getSourceFile: getVersionedSourceFile, + getSourceFileByPath: getVersionedSourceFileByPath, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + getCurrentDirectory: memoize(() => host.getCurrentDirectory()), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists, + readFile: fileName => host.readFile(fileName), + trace: (s: string) => host.write(s + newLine), + directoryExists: directoryName => host.directoryExists(directoryName), + getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => host.getDirectories(path), + realpath, + onReleaseOldSourceFile, + emitWithBuilder + }; + + // TODO: cache module resolution + //if (host.resolveModuleNames) { + // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + //} + //if (host.resolveTypeReferenceDirectives) { + // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { + // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); + // }; + //} + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, data, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); + } + } + + function emitWithBuilder(program: Program): EmitResult { + builder.onProgramUpdateGraph(program); + const filesPendingToEmit = changedFilePaths; + changedFilePaths = []; + + const seenFiles = createMap(); + + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; + let sourceMaps: SourceMapData[]; + while (filesPendingToEmit.length) { + const filePath = filesPendingToEmit.pop(); + const affectedFiles = builder.getFilesAffectedBy(program, filePath); + for (const file of affectedFiles) { + if (!seenFiles.has(file)) { + seenFiles.set(file, true); + const sourceFile = program.getSourceFile(file); + if (sourceFile) { + writeFiles(builder.emitFile(program, sourceFile.path)); + } + } + } + } + + return { emitSkipped, diagnostics, emittedFiles, sourceMaps }; + + function writeFiles(emitOutput: EmitOutputDetailed) { + if (emitOutput.emitSkipped) { + emitSkipped = true; + } + + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + // If it emitted more than one source files, just mark all those source files as seen + if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.fileName, true); + } + } + for (const outputFile of emitOutput.outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + (diagnostics || (diagnostics = [])).push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } + } + } + } + } + + function fileExists(fileName: string) { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const hostSourceFileInfo = sourceFilesCache.get(path); + if (hostSourceFileInfo !== undefined) { + return !isString(hostSourceFileInfo); + } + + return host.fileExists(fileName); + } + + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(host.getExecutingFilePath())); + } + + function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + } + + function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + const hostSourceFile = sourceFilesCache.get(path); + // No source file on the host + if (isString(hostSourceFile)) { + return undefined; + } + + // Create new source file if requested or the versions dont match + if (!hostSourceFile) { + changedFilePaths.push(path); + const sourceFile = getSourceFile(fileName, languageVersion, onError); + if (sourceFile) { + sourceFile.version = "0"; + const fileWatcher = watchSourceFileForChanges(sourceFile.path); + sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + } + else { + sourceFilesCache.set(path, "0"); + } + return sourceFile; + } + else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { + changedFilePaths.push(path); + if (shouldCreateNewSourceFile) { + hostSourceFile.version++; + } + const newSourceFile = getSourceFile(fileName, languageVersion, onError); + if (newSourceFile) { + newSourceFile.version = hostSourceFile.version.toString(); + hostSourceFile.sourceFile = newSourceFile; + } + else { + // File doesnt exist any more + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, hostSourceFile.version.toString()); + } + + return newSourceFile; + } + + return hostSourceFile.sourceFile; + + function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { + let text: string; + try { + performance.mark("beforeIORead"); + text = host.readFile(fileName, compilerOptions.charset); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + text = ""; + } + + return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; + } + } + + function removeSourceFile(path: Path) { + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile !== undefined) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + } + sourceFilesCache.delete(path); + } + } + + function getSourceVersion(path: Path): string { + const hostSourceFile = sourceFilesCache.get(path); + return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); + } + + function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { + const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); + // If this is the source file thats in the cache and new program doesnt need it, + // remove the cached entry. + // Note we arent deleting entry if file became missing in new program or + // there was version update and new source file was created. + if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { + sourceFilesCache.delete(oldSourceFile.path); + } + } + + // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch + // operations (such as saving all modified files in an editor) a chance to complete before we kick + // off a new compilation. + function scheduleProgramUpdate() { + if (!sys.setTimeout || !sys.clearTimeout) { + return; + } + + if (timerToUpdateProgram) { + sys.clearTimeout(timerToUpdateProgram); + } + timerToUpdateProgram = sys.setTimeout(updateProgram, 250); + } + + function scheduleProgramReload() { + Debug.assert(!!configFileName); + needsReload = true; + scheduleProgramUpdate(); + } + + function updateProgram() { + timerToUpdateProgram = undefined; + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + + if (needsReload) { + reloadConfigFile(); + } + else { + synchronizeProgram(); + } + } + + function reloadConfigFile() { + writeLog(`Reloading config file: ${configFileName}`); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + + needsReload = false; + + const cachedHost = host as CachedSystem; + cachedHost.clearCache(); + const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost); + rootFileNames = configParseResult.fileNames; + compilerOptions = configParseResult.options; + configFileSpecs = configParseResult.configFileSpecs; + configFileWildCardDirectories = configParseResult.wildcardDirectories; + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + } + + function watchSourceFileForChanges(path: Path) { + return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); + } + + function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { + writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile) { + // Update the cache + if (eventKind === FileWatcherEventKind.Deleted) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, (hostSourceFile.version++).toString()); + } + } + else { + // Deleted file created + if (isString(hostSourceFile)) { + sourceFilesCache.delete(path); + } + else { + // file changed - just update the version + hostSourceFile.version++; + } + } + } + // Update the program + scheduleProgramUpdate(); + } + + function watchMissingFilePath(missingFilePath: Path) { + return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); + } + + function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { + fileWatcher.close(); + } + + function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { + writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); + if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { + closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); + missingFilesMap.delete(missingFilePath); + + if (configFileName) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); + } + + // Delete the entry in the source files cache so that new source file is created + removeSourceFile(missingFilePath); + + // When a missing file is created, we should update the graph. + scheduleProgramUpdate(); + } + } + + function watchConfigFileWildCardDirectories() { + const wildcards = createMapFromTemplate(configFileWildCardDirectories); + watchedWildCardDirectories = updateWatchingWildcardDirectories( + watchedWildCardDirectories, wildcards, + watchWildCardDirectory, stopWatchingWildCardDirectory + ); + } + + function watchWildCardDirectory(directory: string, recursive: boolean) { + return host.watchDirectory(directory, fileName => + onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), + recursive); + } + + function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) { + fileWatcher.close(); + } + + function onFileAddOrRemoveInWatchedDirectory(fileName: string) { + Debug.assert(!!configFileName); + (host as CachedSystem).addOrDeleteFileOrFolder(fileName); + + // Since the file existance changed, update the sourceFiles cache + removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName)); + + // If a change was made inside "folder/file", node will trigger the callback twice: + // one with the fileName being "folder/file", and the other one with "folder". + // We don't respond to the second one. + if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); + return; + } + + writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); + + // Reload is pending, do the reload + if (!needsReload) { + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []); + if (!configFileSpecs.filesSpecs) { + reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined); + } + rootFileNames = result.fileNames; + + // Schedule Update the program + scheduleProgramUpdate(); + } + } + + function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { + writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + scheduleProgramReload(); + } + + function writeLog(s: string) { + const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + if (hasDiagnostics) { + host.write(s); + } + } + + function computeHash(data: string) { + return sys.createHash ? sys.createHash(data) : data; + } + } + + interface CachedSystem extends System { + addOrDeleteFileOrFolder(fileOrFolder: string): void; + clearCache(): void; + } + + function createCachedSystem(host: System): CachedSystem { + const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; + const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; + const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; + const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; + const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; + const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; + const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; + const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; + const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; + const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; + + const cachedHost = createCachedHost(host); + return { + args: host.args, + newLine: host.newLine, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, + write: s => host.write(s), + readFile: (path, encoding?) => host.readFile(path, encoding), + getFileSize, + writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), + watchFile, + watchDirectory, + resolvePath: path => host.resolvePath(path), + fileExists: fileName => cachedHost.fileExists(fileName), + directoryExists: dir => cachedHost.directoryExists(dir), + createDirectory: dir => cachedHost.createDirectory(dir), + getExecutingFilePath: () => host.getExecutingFilePath(), + getCurrentDirectory: () => cachedHost.getCurrentDirectory(), + getDirectories: dir => cachedHost.getDirectories(dir), + readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), + getModifiedTime, + createHash, + getMemoryUsage, + exit: exitCode => host.exit(exitCode), + realpath, + getEnvironmentVariable: name => host.getEnvironmentVariable(name), + tryEnableSourceMapsForHost, + debugMode: host.debugMode, + setTimeout, + clearTimeout, + addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), + clearCache: () => cachedHost.clearCache() + }; + } + + /* @internal */ + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: System): ParsedCommandLine { + let configFileText: string; + try { + configFileText = host.readFile(configFileName); + } + catch (e) { + const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); + reportWatchDiagnostic(error); + host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + if (!configFileText) { + const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); + reportDiagnostics([error], /* compilerHost */ undefined); + host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + + const result = parseJsonText(configFileName, configFileText); + reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + + const cwd = host.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); + + return configParseResult; + } + + function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) { + const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + let statistics: Statistic[]; + if (hasDiagnostics) { + performance.enable(); + statistics = []; + } + + const program = createProgram(fileNames, compilerOptions, compilerHost, oldProgram); + const exitStatus = compileProgram(); + + if (compilerOptions.listFiles) { + forEach(program.getSourceFiles(), file => { + sys.write(file.fileName + sys.newLine); + }); + } + + if (hasDiagnostics) { + const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; + reportCountStatistic("Files", program.getSourceFiles().length); + reportCountStatistic("Lines", countLines(program)); + reportCountStatistic("Nodes", program.getNodeCount()); + reportCountStatistic("Identifiers", program.getIdentifierCount()); + reportCountStatistic("Symbols", program.getSymbolCount()); + reportCountStatistic("Types", program.getTypeCount()); + + if (memoryUsed >= 0) { + reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); + } + + const programTime = performance.getDuration("Program"); + const bindTime = performance.getDuration("Bind"); + const checkTime = performance.getDuration("Check"); + const emitTime = performance.getDuration("Emit"); + if (compilerOptions.extendedDiagnostics) { + performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + } + else { + // Individual component times. + // Note: To match the behavior of previous versions of the compiler, the reported parse time includes + // I/O read time and processing time for triple-slash references and module imports, and the reported + // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. + reportTimeStatistic("I/O read", performance.getDuration("I/O Read")); + reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); + reportTimeStatistic("Parse time", programTime); + reportTimeStatistic("Bind time", bindTime); + reportTimeStatistic("Check time", checkTime); + reportTimeStatistic("Emit time", emitTime); + } + reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + reportStatistics(); + + performance.disable(); + } + + return { program, exitStatus }; + + function compileProgram(): ExitStatus { + let diagnostics: Diagnostic[]; + + // First get and report any syntactic errors. + diagnostics = program.getSyntacticDiagnostics(); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + diagnostics = program.getSemanticDiagnostics(); + } + } + + // Emit and report any errors we ran into. + let emitOutput: EmitResult; + if (compilerHost.emitWithBuilder) { + emitOutput = compilerHost.emitWithBuilder(program); + } + else { + // Emit whole program + emitOutput = program.emit(); + } + diagnostics = diagnostics.concat(emitOutput.diagnostics); + + reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); + + reportEmittedFiles(emitOutput.emittedFiles); + if (emitOutput.emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; + } + + function reportStatistics() { + let nameSize = 0; + let valueSize = 0; + for (const { name, value } of statistics) { + if (name.length > nameSize) { + nameSize = name.length; + } + + if (value.length > valueSize) { + valueSize = value.length; + } + } + + for (const { name, value } of statistics) { + sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); + } + } + + function reportStatisticalValue(name: string, value: string) { + statistics.push({ name, value }); + } + + function reportCountStatistic(name: string, count: number) { + reportStatisticalValue(name, "" + count); + } + + function reportTimeStatistic(name: string, time: number) { + reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); + } + } + + function printVersion() { + sys.write(getDiagnosticText(Diagnostics.Version_0, ts.version) + sys.newLine); + } + + function printHelp(showAllOptions: boolean) { + const output: string[] = []; + + // We want to align our "syntax" and "examples" commands to a certain margin. + const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; + const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; + let marginLength = Math.max(syntaxLength, examplesLength); + + // Build up the syntactic skeleton. + let syntax = makePadding(marginLength - syntaxLength); + syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; + + output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); + output.push(sys.newLine + sys.newLine); + + // Build up the list of examples. + const padding = makePadding(marginLength); + output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); + output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); + output.push(padding + "tsc @args.txt" + sys.newLine); + output.push(sys.newLine); + + output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); + + // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") + const optsList = showAllOptions ? + optionDeclarations.slice().sort((a, b) => compareValues(a.name.toLowerCase(), b.name.toLowerCase())) : + filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView); + + // We want our descriptions to align at the same column in our output, + // so we keep track of the longest option usage string. + marginLength = 0; + const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. + const descriptionColumn: string[] = []; + + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind + + for (let i = 0; i < optsList.length; i++) { + const option = optsList[i]; + + // If an option lacks a description, + // it is not officially supported. + if (!option.description) { + continue; + } + + let usageText = " "; + if (option.shortName) { + usageText += "-" + option.shortName; + usageText += getParamType(option); + usageText += ", "; + } + + usageText += "--" + option.name; + usageText += getParamType(option); + + usageColumn.push(usageText); + let description: string; + + if (option.name === "lib") { + description = getDiagnosticText(option.description); + const element = (option).element; + const typeMap = >element.type; + optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); + } + else { + description = getDiagnosticText(option.description); + } + + descriptionColumn.push(description); + + // Set the new margin for the description column if necessary. + marginLength = Math.max(usageText.length, marginLength); + } + + // Special case that can't fit in the loop. + const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; + usageColumn.push(usageText); + descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); + marginLength = Math.max(usageText.length, marginLength); + + // Print out each row, aligning all the descriptions on the same column. + for (let i = 0; i < usageColumn.length; i++) { + const usage = usageColumn[i]; + const description = descriptionColumn[i]; + const kindsList = optionsDescriptionMap.get(description); + output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); + + if (kindsList) { + output.push(makePadding(marginLength + 4)); + for (const kind of kindsList) { + output.push(kind + " "); + } + output.push(sys.newLine); + } + } + + for (const line of output) { + sys.write(line); + } + return; + + function getParamType(option: CommandLineOption) { + if (option.paramType !== undefined) { + return " " + getDiagnosticText(option.paramType); + } + return ""; + } + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + } + + function writeConfigFile(options: CompilerOptions, fileNames: string[]) { + const currentDirectory = sys.getCurrentDirectory(); + const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); + if (sys.fileExists(file)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* host */ undefined); + } + else { + sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* host */ undefined); + } + + return; + } +} + +if (ts.Debug.isDebugging) { + ts.Debug.enableDebugInfo(); +} + +if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { + ts.sys.tryEnableSourceMapsForHost(); +} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 6c50bef7216..360819f2b36 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -38,6 +38,7 @@ "program.ts", "builder.ts", "commandLineParser.ts", + "tscLib.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" ] From 69e5abd5b774cf34d9204c1e56faaa5b2f228ed3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jul 2017 11:57:10 -0700 Subject: [PATCH 038/144] Refactor watched system from tsserver tests so that tscLib watch can leverage it --- src/harness/harness.ts | 4 + src/harness/harnessLanguageService.ts | 4 - src/harness/tsconfig.json | 3 +- .../unittests/cachingInServerLSHost.ts | 2 +- src/harness/unittests/session.ts | 2 +- src/harness/unittests/telemetry.ts | 8 +- .../unittests/tsserverProjectSystem.ts | 502 +----------------- src/harness/virtualFileSystemWithWatch.ts | 492 +++++++++++++++++ 8 files changed, 517 insertions(+), 500 deletions(-) create mode 100644 src/harness/virtualFileSystemWithWatch.ts diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 8f4dae70116..c0181199cbe 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -753,6 +753,10 @@ namespace Harness { } } + export function mockHash(s: string): string { + return `hash-${s}`; + } + const environment = Utils.getExecutionEnvironment(); switch (environment) { case Utils.ExecutionEnvironment.Node: diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c604b224656..22878f5a89e 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -853,8 +853,4 @@ namespace Harness.LanguageService { getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } } - - export function mockHash(s: string): string { - return `hash-${s}`; - } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 66ca2fc3f48..dfdd0a2fee8 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -1,4 +1,4 @@ -{ +{ "extends": "../tsconfig-base", "compilerOptions": { "removeComments": false, @@ -44,6 +44,7 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/builder.ts", "../compiler/commandLineParser.ts", "../compiler/diagnosticInformationMap.generated.ts", "../services/breakpoints.ts", diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 5265a12355e..12efa5717a3 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -47,7 +47,7 @@ namespace ts { clearTimeout, setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0), clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout, - createHash: Harness.LanguageService.mockHash, + createHash: Harness.mockHash, }; } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 862ebee4b03..31de6356090 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -25,7 +25,7 @@ namespace ts.server { clearTimeout: noop, setImmediate: () => 0, clearImmediate: noop, - createHash: Harness.LanguageService.mockHash, + createHash: Harness.mockHash, }; const mockLogger: Logger = { diff --git a/src/harness/unittests/telemetry.ts b/src/harness/unittests/telemetry.ts index a8562503074..e7a64c59903 100644 --- a/src/harness/unittests/telemetry.ts +++ b/src/harness/unittests/telemetry.ts @@ -53,7 +53,7 @@ namespace ts.projectSystem { // TODO: Apparently compilerOptions is mutated, so have to repeat it here! et.assertProjectInfoTelemetryEvent({ - projectId: Harness.LanguageService.mockHash("/hunter2/foo.csproj"), + projectId: Harness.mockHash("/hunter2/foo.csproj"), compilerOptions: { strict: true }, compileOnSave: true, // These properties can't be present for an external project, so they are undefined instead of false. @@ -195,7 +195,7 @@ namespace ts.projectSystem { const et = new EventTracker([jsconfig, file]); et.service.openClientFile(file.path); et.assertProjectInfoTelemetryEvent({ - projectId: Harness.LanguageService.mockHash("/jsconfig.json"), + projectId: Harness.mockHash("/jsconfig.json"), fileStats: fileStats({ js: 1 }), compilerOptions: autoJsCompilerOptions, typeAcquisition: { @@ -215,7 +215,7 @@ namespace ts.projectSystem { et.service.openClientFile(file.path); et.getEvent(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true); et.assertProjectInfoTelemetryEvent({ - projectId: Harness.LanguageService.mockHash("/jsconfig.json"), + projectId: Harness.mockHash("/jsconfig.json"), fileStats: fileStats({ js: 1 }), compilerOptions: autoJsCompilerOptions, configFileName: "jsconfig.json", @@ -251,7 +251,7 @@ namespace ts.projectSystem { assertProjectInfoTelemetryEvent(partial: Partial): void { assert.deepEqual(this.getEvent(ts.server.ProjectInfoTelemetryEvent), { - projectId: Harness.LanguageService.mockHash("/tsconfig.json"), + projectId: Harness.mockHash("/tsconfig.json"), fileStats: fileStats({ ts: 1 }), compilerOptions: {}, extends: false, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index ca86138803c..b7c71f28191 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1,4 +1,5 @@ /// +/// /// namespace ts.projectSystem { @@ -6,17 +7,18 @@ namespace ts.projectSystem { import protocol = server.protocol; import CommandNames = server.CommandNames; - const safeList = { - path: "/safeList.json", - content: JSON.stringify({ - commander: "commander", - express: "express", - jquery: "jquery", - lodash: "lodash", - moment: "moment", - chroma: "chroma-js" - }) - }; + export import TestServerHost = ts.TestFSWithWatch.TestServerHost; + export type TestServerHostCreationParameters = ts.TestFSWithWatch.TestServerHostCreationParameters; + export type File = ts.TestFSWithWatch.File; + export type FileOrFolder = ts.TestFSWithWatch.FileOrFolder; + export type Folder = ts.TestFSWithWatch.Folder; + export type FSEntry = ts.TestFSWithWatch.FSEntry; + export import createServerHost = ts.TestFSWithWatch.createServerHost; + export import checkFileNames = ts.TestFSWithWatch.checkFileNames; + export import libFile = ts.TestFSWithWatch.libFile; + export import checkWatchedFiles = ts.TestFSWithWatch.checkWatchedFiles; + export import checkWatchedDirectories = ts.TestFSWithWatch.checkWatchedDirectories; + import safeList = ts.TestFSWithWatch.safeList; const customSafeList = { path: "/typeMapList.json", @@ -45,12 +47,6 @@ namespace ts.projectSystem { getLogFileName: (): string => undefined }; - export const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO); - export const libFile: FileOrFolder = { - path: "/a/lib/lib.d.ts", - content: libFileContent - }; - export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { protected projectService: server.ProjectService; constructor( @@ -118,10 +114,6 @@ namespace ts.projectSystem { return JSON.stringify({ dependencies }); } - export function getExecutingFilePathFromLibFile(): string { - return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); - } - export function toExternalFile(fileName: string): protocol.ExternalFile { return { fileName }; } @@ -143,26 +135,6 @@ namespace ts.projectSystem { } } - export interface TestServerHostCreationParameters { - useCaseSensitiveFileNames?: boolean; - executingFilePath?: string; - currentDirectory?: string; - newLine?: string; - } - - export function createServerHost(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost { - if (!params) { - params = {}; - } - const host = new TestServerHost( - params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, - params.executingFilePath || getExecutingFilePathFromLibFile(), - params.currentDirectory || "/", - fileOrFolderList, - params.newLine); - return host; - } - class TestSession extends server.Session { private seq = 0; @@ -213,7 +185,6 @@ namespace ts.projectSystem { eventHandler?: server.ProjectServiceEventHandler; } - export class TestProjectService extends server.ProjectService { constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler) { @@ -233,67 +204,6 @@ namespace ts.projectSystem { return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, parameters.typingsInstaller, parameters.eventHandler); } - export interface FileOrFolder { - path: string; - content?: string; - fileSize?: number; - } - - export interface FSEntry { - path: Path; - fullPath: string; - } - - export interface File extends FSEntry { - content: string; - fileSize?: number; - } - - export interface Folder extends FSEntry { - entries: FSEntry[]; - } - - export function isFolder(s: FSEntry): s is Folder { - return s && isArray((s).entries); - } - - export function isFile(s: FSEntry): s is File { - return s && isString((s).content); - } - - function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) { - if (callbacks) { - const cbs = callbacks.slice(); - for (const cb of cbs) { - const fileName = getRelativeFilePath(); - cb(fileName); - } - } - } - - function invokeFileWatcher(callbacks: FileWatcherCallback[], fileName: string, eventId: FileWatcherEventKind) { - if (callbacks) { - const cbs = callbacks.slice(); - for (const cb of cbs) { - cb(fileName, eventId); - } - } - } - - export function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`); - for (const name of expectedKeys) { - assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); - } - } - - export function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) { - assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`); - for (const f of expectedFileNames) { - assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`); - } - } - export function checkNumberOfConfiguredProjects(projectService: server.ProjectService, expected: number) { assert.equal(projectService.configuredProjects.size, expected, `expected ${expected} configured project(s)`); } @@ -321,14 +231,6 @@ namespace ts.projectSystem { return values.next().value; } - export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) { - checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); - } - - export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) { - checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); - } - export function checkProjectActualFiles(project: server.Project, expectedFiles: string[]) { checkFileNames(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); } @@ -337,384 +239,6 @@ namespace ts.projectSystem { checkFileNames(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); } - export class Callbacks { - private map: TimeOutCallback[] = []; - private nextId = 1; - - register(cb: (...args: any[]) => void, args: any[]) { - const timeoutId = this.nextId; - this.nextId++; - this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args); - return timeoutId; - } - - unregister(id: any) { - if (typeof id === "number") { - delete this.map[id]; - } - } - - count() { - let n = 0; - for (const _ in this.map) { - n++; - } - return n; - } - - invoke() { - // Note: invoking a callback may result in new callbacks been queued, - // so do not clear the entire callback list regardless. Only remove the - // ones we have invoked. - for (const key in this.map) { - this.map[key](); - delete this.map[key]; - } - } - } - - export type TimeOutCallback = () => any; - - export class TestServerHost implements server.ServerHost { - args: string[] = []; - - private readonly output: string[] = []; - - private fs: Map = createMap(); - private getCanonicalFileName: (s: string) => string; - private toPath: (f: string) => Path; - private timeoutCallbacks = new Callbacks(); - private immediateCallbacks = new Callbacks(); - - readonly watchedDirectories = createMultiMap(); - readonly watchedDirectoriesRecursive = createMultiMap(); - readonly watchedFiles = createMultiMap(); - - constructor(public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") { - this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); - this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName); - - this.reloadFS(fileOrFolderList); - } - - private toFullPath(s: string) { - const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); - return this.toPath(fullPath); - } - - reloadFS(fileOrFolderList: FileOrFolder[]) { - const mapNewLeaves = createMap(); - const isNewFs = this.fs.size === 0; - // always inject safelist file in the list of files - for (const fileOrFolder of fileOrFolderList.concat(safeList)) { - const path = this.toFullPath(fileOrFolder.path); - mapNewLeaves.set(path, true); - // If its a change - const currentEntry = this.fs.get(path); - if (currentEntry) { - if (isFile(currentEntry)) { - if (isString(fileOrFolder.content)) { - // Update file - if (currentEntry.content !== fileOrFolder.content) { - currentEntry.content = fileOrFolder.content; - this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); - } - } - else { - // TODO: Changing from file => folder - } - } - else { - // Folder - if (isString(fileOrFolder.content)) { - // TODO: Changing from folder => file - } - else { - // Folder update: Nothing to do. - } - } - } - else { - this.ensureFileOrFolder(fileOrFolder); - } - } - - if (!isNewFs) { - this.fs.forEach((fileOrFolder, path) => { - // If this entry is not from the new file or folder - if (!mapNewLeaves.get(path)) { - // Leaf entries that arent in new list => remove these - if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) { - this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path)); - } - } - }); - } - } - - ensureFileOrFolder(fileOrFolder: FileOrFolder) { - if (isString(fileOrFolder.content)) { - const file = this.toFile(fileOrFolder); - Debug.assert(!this.fs.get(file.path)); - const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); - this.addFileOrFolderInFolder(baseFolder, file); - } - else { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); - this.ensureFolder(fullPath); - } - } - - private ensureFolder(fullPath: string): Folder { - const path = this.toPath(fullPath); - let folder = this.fs.get(path) as Folder; - if (!folder) { - folder = this.toFolder(fullPath); - const baseFullPath = getDirectoryPath(fullPath); - if (fullPath !== baseFullPath) { - // Add folder in the base folder - const baseFolder = this.ensureFolder(baseFullPath); - this.addFileOrFolderInFolder(baseFolder, folder); - } - else { - // root folder - Debug.assert(this.fs.size === 0); - this.fs.set(path, folder); - } - } - Debug.assert(isFolder(folder)); - return folder; - } - - private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) { - folder.entries.push(fileOrFolder); - this.fs.set(fileOrFolder.path, fileOrFolder); - - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created); - } - this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath); - } - - private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { - const basePath = getDirectoryPath(fileOrFolder.path); - const baseFolder = this.fs.get(basePath) as Folder; - if (basePath !== fileOrFolder.path) { - Debug.assert(!!baseFolder); - filterMutate(baseFolder.entries, entry => entry !== fileOrFolder); - } - this.fs.delete(fileOrFolder.path); - - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted); - } - else { - Debug.assert(fileOrFolder.entries.length === 0); - invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); - } - - if (basePath !== fileOrFolder.path) { - if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) { - this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); - } - else { - this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath); - } - } - } - - private invokeFileWatcher(fileFullPath: string, eventId: FileWatcherEventKind) { - const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - invokeFileWatcher(callbacks, getBaseFileName(fileFullPath), eventId); - } - - private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { - return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); - } - - private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectories.get(this.toPath(folderFullPath)), () => this.getRelativePathToDirectory(folderFullPath, fileName)); - this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); - } - - private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), () => this.getRelativePathToDirectory(fullPath, fileName)); - const basePath = getDirectoryPath(fullPath); - if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { - this.invokeRecursiveDirectoryWatcher(basePath, fileName); - } - } - - private toFile(fileOrFolder: FileOrFolder): File { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); - return { - path: this.toPath(fullPath), - content: fileOrFolder.content, - fullPath, - fileSize: fileOrFolder.fileSize - }; - } - - private toFolder(path: string): Folder { - const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory); - return { - path: this.toPath(fullPath), - entries: [], - fullPath - }; - } - - fileExists(s: string) { - const path = this.toFullPath(s); - return isFile(this.fs.get(path)); - } - - getFileSize(s: string) { - const path = this.toFullPath(s); - const entry = this.fs.get(path); - if (isFile(entry)) { - return entry.fileSize ? entry.fileSize : entry.content.length; - } - return undefined; - } - - directoryExists(s: string) { - const path = this.toFullPath(s); - return isFolder(this.fs.get(path)); - } - - getDirectories(s: string) { - const path = this.toFullPath(s); - const folder = this.fs.get(path); - if (isFolder(folder)) { - return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); - } - Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); - return []; - } - - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { - const directories: string[] = []; - const files: string[] = []; - const dirEntry = this.fs.get(this.toPath(dir)); - if (isFolder(dirEntry)) { - dirEntry.entries.forEach((entry) => { - if (isFolder(entry)) { - directories.push(getBaseFileName(entry.fullPath)); - } - else if (isFile(entry)) { - files.push(getBaseFileName(entry.fullPath)); - } - else { - Debug.fail("Unknown entry"); - } - }); - } - return { directories, files }; - }); - } - - watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { - const path = this.toFullPath(directoryName); - const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; - map.add(path, callback); - return { - referenceCount: 0, - directoryName, - close: () => map.remove(path, callback) - }; - } - - createHash(s: string): string { - return Harness.LanguageService.mockHash(s); - } - - watchFile(fileName: string, callback: FileWatcherCallback) { - const path = this.toFullPath(fileName); - this.watchedFiles.add(path, callback); - return { close: () => this.watchedFiles.remove(path, callback) }; - } - - // TOOD: record and invoke callbacks to simulate timer events - setTimeout(callback: TimeOutCallback, _time: number, ...args: any[]) { - return this.timeoutCallbacks.register(callback, args); - } - - clearTimeout(timeoutId: any): void { - this.timeoutCallbacks.unregister(timeoutId); - } - - checkTimeoutQueueLengthAndRun(expected: number) { - this.checkTimeoutQueueLength(expected); - this.runQueuedTimeoutCallbacks(); - } - - checkTimeoutQueueLength(expected: number) { - const callbacksCount = this.timeoutCallbacks.count(); - assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`); - } - - runQueuedTimeoutCallbacks() { - this.timeoutCallbacks.invoke(); - } - - runQueuedImmediateCallbacks() { - this.immediateCallbacks.invoke(); - } - - setImmediate(callback: TimeOutCallback, _time: number, ...args: any[]) { - return this.immediateCallbacks.register(callback, args); - } - - clearImmediate(timeoutId: any): void { - this.immediateCallbacks.unregister(timeoutId); - } - - createDirectory(directoryName: string): void { - const folder = this.toFolder(directoryName); - - // base folder has to be present - const base = getDirectoryPath(folder.fullPath); - const baseFolder = this.fs.get(base) as Folder; - Debug.assert(isFolder(baseFolder)); - - Debug.assert(!this.fs.get(folder.path), isFile(this.fs.get(folder.path)) ? `Found the file ${folder.path}` : `Found the folder ${folder.path}`); - this.addFileOrFolderInFolder(baseFolder, folder); - } - - writeFile(path: string, content: string): void { - const file = this.toFile({ path, content }); - - // base folder has to be present - const base = getDirectoryPath(file.fullPath); - const folder = this.fs.get(base) as Folder; - Debug.assert(isFolder(folder)); - - this.addFileOrFolderInFolder(folder, file); - } - - write(message: string) { - this.output.push(message); - } - - getOutput(): ReadonlyArray { - return this.output; - } - - clearOutput() { - clear(this.output); - } - - readonly readFile = (s: string) => (this.fs.get(this.toFullPath(s))).content; - readonly resolvePath = (s: string) => s; - readonly getExecutingFilePath = () => this.executingFilePath; - readonly getCurrentDirectory = () => this.currentDirectory; - readonly exit = notImplemented; - readonly getEnvironmentVariable = notImplemented; - } - /** * Test server cancellation token used to mock host token cancellation requests. * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts new file mode 100644 index 00000000000..f37d685851c --- /dev/null +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -0,0 +1,492 @@ +/// + +namespace ts.TestFSWithWatch { + export const { content: libFileContent } = Harness.getDefaultLibraryFile(Harness.IO); + export const libFile: FileOrFolder = { + path: "/a/lib/lib.d.ts", + content: libFileContent + }; + + export const safeList = { + path: "/safeList.json", + content: JSON.stringify({ + commander: "commander", + express: "express", + jquery: "jquery", + lodash: "lodash", + moment: "moment", + chroma: "chroma-js" + }) + }; + + export function getExecutingFilePathFromLibFile(): string { + return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); + } + + export interface TestServerHostCreationParameters { + useCaseSensitiveFileNames?: boolean; + executingFilePath?: string; + currentDirectory?: string; + newLine?: string; + } + + export function createServerHost(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost { + if (!params) { + params = {}; + } + const host = new TestServerHost(/*withSafelist*/ true, + params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, + params.executingFilePath || getExecutingFilePathFromLibFile(), + params.currentDirectory || "/", + fileOrFolderList, + params.newLine); + return host; + } + + export interface FileOrFolder { + path: string; + content?: string; + fileSize?: number; + } + + export interface FSEntry { + path: Path; + fullPath: string; + } + + export interface File extends FSEntry { + content: string; + fileSize?: number; + } + + export interface Folder extends FSEntry { + entries: FSEntry[]; + } + + export function isFolder(s: FSEntry): s is Folder { + return s && isArray((s).entries); + } + + export function isFile(s: FSEntry): s is File { + return s && isString((s).content); + } + + function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) { + if (callbacks) { + const cbs = callbacks.slice(); + for (const cb of cbs) { + const fileName = getRelativeFilePath(); + cb(fileName); + } + } + } + + function invokeFileWatcher(callbacks: FileWatcherCallback[], fileName: string, eventId: FileWatcherEventKind) { + if (callbacks) { + const cbs = callbacks.slice(); + for (const cb of cbs) { + cb(fileName, eventId); + } + } + } + + export function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { + assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`); + for (const name of expectedKeys) { + assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); + } + } + + export function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) { + assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`); + for (const f of expectedFileNames) { + assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`); + } + } + + export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) { + checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles); + } + + export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) { + checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); + } + + export class Callbacks { + private map: TimeOutCallback[] = []; + private nextId = 1; + + register(cb: (...args: any[]) => void, args: any[]) { + const timeoutId = this.nextId; + this.nextId++; + this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args); + return timeoutId; + } + + unregister(id: any) { + if (typeof id === "number") { + delete this.map[id]; + } + } + + count() { + let n = 0; + for (const _ in this.map) { + n++; + } + return n; + } + + invoke() { + // Note: invoking a callback may result in new callbacks been queued, + // so do not clear the entire callback list regardless. Only remove the + // ones we have invoked. + for (const key in this.map) { + this.map[key](); + delete this.map[key]; + } + } + } + + export type TimeOutCallback = () => any; + + export class TestServerHost implements server.ServerHost { + args: string[] = []; + + private readonly output: string[] = []; + + private fs: Map = createMap(); + private getCanonicalFileName: (s: string) => string; + private toPath: (f: string) => Path; + private timeoutCallbacks = new Callbacks(); + private immediateCallbacks = new Callbacks(); + + readonly watchedDirectories = createMultiMap(); + readonly watchedDirectoriesRecursive = createMultiMap(); + readonly watchedFiles = createMultiMap(); + + constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") { + this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName); + + this.reloadFS(fileOrFolderList); + } + + private toFullPath(s: string) { + const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); + return this.toPath(fullPath); + } + + reloadFS(fileOrFolderList: FileOrFolder[]) { + const mapNewLeaves = createMap(); + const isNewFs = this.fs.size === 0; + // always inject safelist file in the list of files + for (const fileOrFolder of fileOrFolderList.concat(this.withSafeList ? safeList : [])) { + const path = this.toFullPath(fileOrFolder.path); + mapNewLeaves.set(path, true); + // If its a change + const currentEntry = this.fs.get(path); + if (currentEntry) { + if (isFile(currentEntry)) { + if (isString(fileOrFolder.content)) { + // Update file + if (currentEntry.content !== fileOrFolder.content) { + currentEntry.content = fileOrFolder.content; + this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); + } + } + else { + // TODO: Changing from file => folder + } + } + else { + // Folder + if (isString(fileOrFolder.content)) { + // TODO: Changing from folder => file + } + else { + // Folder update: Nothing to do. + } + } + } + else { + this.ensureFileOrFolder(fileOrFolder); + } + } + + if (!isNewFs) { + this.fs.forEach((fileOrFolder, path) => { + // If this entry is not from the new file or folder + if (!mapNewLeaves.get(path)) { + // Leaf entries that arent in new list => remove these + if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) { + this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path)); + } + } + }); + } + } + + ensureFileOrFolder(fileOrFolder: FileOrFolder) { + if (isString(fileOrFolder.content)) { + const file = this.toFile(fileOrFolder); + Debug.assert(!this.fs.get(file.path)); + const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); + this.addFileOrFolderInFolder(baseFolder, file); + } + else { + const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + this.ensureFolder(fullPath); + } + } + + private ensureFolder(fullPath: string): Folder { + const path = this.toPath(fullPath); + let folder = this.fs.get(path) as Folder; + if (!folder) { + folder = this.toFolder(fullPath); + const baseFullPath = getDirectoryPath(fullPath); + if (fullPath !== baseFullPath) { + // Add folder in the base folder + const baseFolder = this.ensureFolder(baseFullPath); + this.addFileOrFolderInFolder(baseFolder, folder); + } + else { + // root folder + Debug.assert(this.fs.size === 0); + this.fs.set(path, folder); + } + } + Debug.assert(isFolder(folder)); + return folder; + } + + private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) { + folder.entries.push(fileOrFolder); + this.fs.set(fileOrFolder.path, fileOrFolder); + + if (isFile(fileOrFolder)) { + this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created); + } + this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath); + } + + private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { + const basePath = getDirectoryPath(fileOrFolder.path); + const baseFolder = this.fs.get(basePath) as Folder; + if (basePath !== fileOrFolder.path) { + Debug.assert(!!baseFolder); + filterMutate(baseFolder.entries, entry => entry !== fileOrFolder); + } + this.fs.delete(fileOrFolder.path); + + if (isFile(fileOrFolder)) { + this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted); + } + else { + Debug.assert(fileOrFolder.entries.length === 0); + invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); + } + + if (basePath !== fileOrFolder.path) { + if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) { + this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); + } + else { + this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath); + } + } + } + + private invokeFileWatcher(fileFullPath: string, eventId: FileWatcherEventKind) { + const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); + invokeFileWatcher(callbacks, getBaseFileName(fileFullPath), eventId); + } + + private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { + return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); + } + + private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectories.get(this.toPath(folderFullPath)), () => this.getRelativePathToDirectory(folderFullPath, fileName)); + this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); + } + + private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { + invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), () => this.getRelativePathToDirectory(fullPath, fileName)); + const basePath = getDirectoryPath(fullPath); + if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { + this.invokeRecursiveDirectoryWatcher(basePath, fileName); + } + } + + private toFile(fileOrFolder: FileOrFolder): File { + const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + return { + path: this.toPath(fullPath), + content: fileOrFolder.content, + fullPath, + fileSize: fileOrFolder.fileSize + }; + } + + private toFolder(path: string): Folder { + const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory); + return { + path: this.toPath(fullPath), + entries: [], + fullPath + }; + } + + fileExists(s: string) { + const path = this.toFullPath(s); + return isFile(this.fs.get(path)); + } + + getFileSize(s: string) { + const path = this.toFullPath(s); + const entry = this.fs.get(path); + if (isFile(entry)) { + return entry.fileSize ? entry.fileSize : entry.content.length; + } + return undefined; + } + + directoryExists(s: string) { + const path = this.toFullPath(s); + return isFolder(this.fs.get(path)); + } + + getDirectories(s: string) { + const path = this.toFullPath(s); + const folder = this.fs.get(path); + if (isFolder(folder)) { + return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); + } + Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); + return []; + } + + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { + return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { + const directories: string[] = []; + const files: string[] = []; + const dirEntry = this.fs.get(this.toPath(dir)); + if (isFolder(dirEntry)) { + dirEntry.entries.forEach((entry) => { + if (isFolder(entry)) { + directories.push(getBaseFileName(entry.fullPath)); + } + else if (isFile(entry)) { + files.push(getBaseFileName(entry.fullPath)); + } + else { + Debug.fail("Unknown entry"); + } + }); + } + return { directories, files }; + }); + } + + watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { + const path = this.toFullPath(directoryName); + const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; + map.add(path, callback); + return { + referenceCount: 0, + directoryName, + close: () => map.remove(path, callback) + }; + } + + createHash(s: string): string { + return Harness.mockHash(s); + } + + watchFile(fileName: string, callback: FileWatcherCallback) { + const path = this.toFullPath(fileName); + this.watchedFiles.add(path, callback); + return { close: () => this.watchedFiles.remove(path, callback) }; + } + + // TOOD: record and invoke callbacks to simulate timer events + setTimeout(callback: TimeOutCallback, _time: number, ...args: any[]) { + return this.timeoutCallbacks.register(callback, args); + } + + clearTimeout(timeoutId: any): void { + this.timeoutCallbacks.unregister(timeoutId); + } + + checkTimeoutQueueLengthAndRun(expected: number) { + this.checkTimeoutQueueLength(expected); + this.runQueuedTimeoutCallbacks(); + } + + checkTimeoutQueueLength(expected: number) { + const callbacksCount = this.timeoutCallbacks.count(); + assert.equal(callbacksCount, expected, `expected ${expected} timeout callbacks queued but found ${callbacksCount}.`); + } + + runQueuedTimeoutCallbacks() { + this.timeoutCallbacks.invoke(); + } + + runQueuedImmediateCallbacks() { + this.immediateCallbacks.invoke(); + } + + setImmediate(callback: TimeOutCallback, _time: number, ...args: any[]) { + return this.immediateCallbacks.register(callback, args); + } + + clearImmediate(timeoutId: any): void { + this.immediateCallbacks.unregister(timeoutId); + } + + createDirectory(directoryName: string): void { + const folder = this.toFolder(directoryName); + + // base folder has to be present + const base = getDirectoryPath(folder.fullPath); + const baseFolder = this.fs.get(base) as Folder; + Debug.assert(isFolder(baseFolder)); + + Debug.assert(!this.fs.get(folder.path), isFile(this.fs.get(folder.path)) ? `Found the file ${folder.path}` : `Found the folder ${folder.path}`); + this.addFileOrFolderInFolder(baseFolder, folder); + } + + writeFile(path: string, content: string): void { + const file = this.toFile({ path, content }); + + // base folder has to be present + const base = getDirectoryPath(file.fullPath); + const folder = this.fs.get(base) as Folder; + Debug.assert(isFolder(folder)); + + this.addFileOrFolderInFolder(folder, file); + } + + write(message: string) { + this.output.push(message); + } + + getOutput(): ReadonlyArray { + return this.output; + } + + clearOutput() { + clear(this.output); + } + + readonly readFile = (s: string) => (this.fs.get(this.toFullPath(s))).content; + readonly resolvePath = (s: string) => s; + readonly getExecutingFilePath = () => this.executingFilePath; + readonly getCurrentDirectory = () => this.currentDirectory; + readonly exit = notImplemented; + readonly getEnvironmentVariable = notImplemented; + } +} From c814d8e847e559f14ed44ced735a8f7ebd79d42c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 26 Jul 2017 15:12:03 -0700 Subject: [PATCH 039/144] Add tests for the tsc --watch --- Jakefile.js | 2 + src/compiler/program.ts | 24 +- src/compiler/tsc.ts | 3 + src/compiler/tscLib.ts | 224 ++--- src/harness/tsconfig.json | 2 + src/harness/unittests/tscWatchMode.ts | 1000 +++++++++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 55 +- src/services/services.ts | 19 +- 8 files changed, 1204 insertions(+), 125 deletions(-) create mode 100644 src/harness/unittests/tscWatchMode.ts diff --git a/Jakefile.js b/Jakefile.js index 31243f77c42..ba3e18f3e56 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -91,6 +91,7 @@ var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, " var harnessCoreSources = [ "harness.ts", "virtualFileSystem.ts", + "virtualFileSystemWithWatch.ts", "sourceMapRecorder.ts", "harnessLanguageService.ts", "fourslash.ts", @@ -128,6 +129,7 @@ var harnessSources = harnessCoreSources.concat([ "convertCompilerOptionsFromJson.ts", "convertTypeAcquisitionFromJson.ts", "tsserverProjectSystem.ts", + "tscWatchMode.ts", "compileOnSave.ts", "typingsInstaller.ts", "projectErrors.ts", diff --git a/src/compiler/program.ts b/src/compiler/program.ts index e2bfa647eb7..80bd089429a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -387,7 +387,8 @@ namespace ts { allDiagnostics?: Diagnostic[]; } - export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string): boolean { + export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, + getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean): boolean { // If we haven't create a program yet, then it is not up-to-date if (!program) { return false; @@ -398,14 +399,18 @@ namespace ts { return false; } - const fileNames = concatenate(rootFileNames, map(program.getSourceFiles(), sourceFile => sourceFile.fileName)); // If any file is not up-to-date, then the whole program is not up-to-date - for (const fileName of fileNames) { - if (!sourceFileUpToDate(program.getSourceFile(fileName))) { + for (const file of program.getSourceFiles()) { + if (!sourceFileUpToDate(program.getSourceFile(file.fileName))) { return false; } } + // If any of the missing file paths are now created + if (program.getMissingFilePaths().some(missingFilePath => fileExists(missingFilePath))) { + return false; + } + const currentOptions = program.getCompilerOptions(); // If the compilation settings do no match, then the program is not up-to-date if (!compareDataObjects(currentOptions, newOptions)) { @@ -445,14 +450,10 @@ namespace ts { /** * Updates the existing missing file watches with the new set of missing files after new program is created - * @param program - * @param existingMap - * @param createMissingFileWatch - * @param closeExistingFileWatcher */ export function updateMissingFilePathsWatch(program: Program, existingMap: Map, createMissingFileWatch: (missingFilePath: Path) => FileWatcher, - closeExistingFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) { + closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void) { const missingFilePaths = program.getMissingFilePaths(); const newMissingFilePathMap = arrayToSet(missingFilePaths); @@ -463,12 +464,15 @@ namespace ts { createMissingFileWatch, // Files that are no longer missing (e.g. because they are no longer required) // should no longer be watched. - closeExistingFileWatcher + closeExistingMissingFilePathFileWatcher ); } export type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean }; + /** + * Updates the existing wild card directory watcyhes with the new set of wild card directories from the config file after new program is created + */ export function updateWatchingWildcardDirectories(existingWatchedForWildcards: Map, wildcardDirectories: Map, watchDirectory: (directory: string, recursive: boolean) => FileWatcher, closeDirectoryWatcher: (directory: string, watcher: FileWatcher, recursive: boolean, recursiveChanged: boolean) => void) { diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ec93b61bbdf..3a672fca727 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -1,3 +1,6 @@ /// +if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { + ts.sys.tryEnableSourceMapsForHost(); +} ts.executeCommandLine(ts.sys.args); diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts index 3dba8961674..b9234906176 100644 --- a/src/compiler/tscLib.ts +++ b/src/compiler/tscLib.ts @@ -12,35 +12,49 @@ namespace ts { value: string; } - const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = { + export interface FormatDiagnosticsHostWithWrite extends FormatDiagnosticsHost { + write?(s: string): void; + } + + const defaultFormatDiagnosticsHost: FormatDiagnosticsHostWithWrite = sys ? { getCurrentDirectory: () => sys.getCurrentDirectory(), getNewLine: () => sys.newLine, - getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) - }; + getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames), + write: s => sys.write(s) + } : undefined; + + function getDefaultFormatDiagnosticsHost(system: System): FormatDiagnosticsHostWithWrite { + return system === sys ? defaultFormatDiagnosticsHost : { + getCurrentDirectory: () => system.getCurrentDirectory(), + getNewLine: () => system.newLine, + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + write: s => system.write(s) + }; + } let reportDiagnosticWorker = reportDiagnosticSimply; - function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) { + function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite) { reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost); } - function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void { + function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHostWithWrite): void { for (const diagnostic of diagnostics) { reportDiagnostic(diagnostic, host); } } - function reportEmittedFiles(files: string[]): void { + function reportEmittedFiles(files: string[], system: System): void { if (!files || files.length === 0) { return; } - const currentDir = sys.getCurrentDirectory(); + const currentDir = system.getCurrentDirectory(); for (const file of files) { const filepath = getNormalizedAbsolutePath(file, currentDir); - sys.write(`TSFILE: ${filepath}${sys.newLine}`); + system.write(`TSFILE: ${filepath}${system.newLine}`); } } @@ -57,15 +71,15 @@ namespace ts { return diagnostic.messageText; } - function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { - sys.write(ts.formatDiagnostics([diagnostic], host)); + function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void { + host.write(ts.formatDiagnostics([diagnostic], host)); } - function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void { - sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine); + function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void { + host.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); } - function reportWatchDiagnostic(diagnostic: Diagnostic) { + function reportWatchDiagnostic(diagnostic: Diagnostic, system: System) { let output = new Date().toLocaleTimeString() + " - "; if (diagnostic.file) { @@ -73,9 +87,9 @@ namespace ts { output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `; } - output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`; + output += `${ flattenDiagnosticMessageText(diagnostic.messageText, system.newLine) }${ system.newLine + system.newLine + system.newLine }`; - sys.write(output); + system.write(output); } function padLeft(s: string, length: number) { @@ -175,7 +189,7 @@ namespace ts { const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithConfigFile(configParseResult, commandLineOptions); + createWatchModeWithConfigFile(configParseResult, commandLineOptions, sys); } else { performCompilation(configParseResult.fileNames, configParseResult.options); @@ -184,7 +198,7 @@ namespace ts { else { if (isWatchSet(commandLine.options)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions); + createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, sys); } else { performCompilation(commandLine.fileNames, commandLineOptions); @@ -204,7 +218,7 @@ namespace ts { } const compilerHost = createCompilerHost(compilerOptions); - const compileResult = compile(rootFileNames, compilerOptions, compilerHost); + const compileResult = compile(rootFileNames, compilerOptions, compilerHost, sys); return sys.exit(compileResult.exitStatus); } } @@ -216,16 +230,16 @@ namespace ts { } /* @internal */ - export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend?: CompilerOptions) { - return createWatchMode(configParseResult.fileNames, configParseResult.options, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); + export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions, system: System) { + return createWatchMode(configParseResult.fileNames, configParseResult.options, system, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); } /* @internal */ - export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions) { - return createWatchMode(rootFileNames, compilerOptions); + export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, system: System) { + return createWatchMode(rootFileNames, compilerOptions, system); } - function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { + function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, system: System, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { let program: Program; let needsReload: boolean; // true if the config file changed and needs to reload it from the disk let missingFilesMap: Map; // Map of file watchers for the missing files @@ -238,11 +252,11 @@ namespace ts { let host: System; if (configFileName) { - host = createCachedSystem(sys); - configFileWatcher = sys.watchFile(configFileName, onConfigFileChanged); + host = createCachedSystem(system); + configFileWatcher = system.watchFile(configFileName, onConfigFileChanged); } else { - host = sys; + host = system; } const currentDirectory = host.getCurrentDirectory(); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); @@ -259,21 +273,23 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); + return () => program; + function synchronizeProgram() { writeLog(`Synchronizing program`); - if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion)) { + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) { return; } // Create the compiler host const compilerHost = createWatchedCompilerHost(compilerOptions); - program = compile(rootFileNames, compilerOptions, compilerHost, program).program; + program = compile(rootFileNames, compilerOptions, compilerHost, system, program).program; // Update watches missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes), system); } function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { @@ -302,14 +318,14 @@ namespace ts { }; // TODO: cache module resolution - //if (host.resolveModuleNames) { - // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); - //} - //if (host.resolveTypeReferenceDirectives) { - // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { - // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); - // }; - //} + // if (host.resolveModuleNames) { + // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + // } + // if (host.resolveTypeReferenceDirectives) { + // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { + // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); + // }; + // } function ensureDirectoriesExist(directoryPath: string) { if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { @@ -342,7 +358,7 @@ namespace ts { const seenFiles = createMap(); let emitSkipped: boolean; - let diagnostics: Diagnostic[]; + const diagnostics: Diagnostic[] = []; const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; let sourceMaps: SourceMapData[]; while (filesPendingToEmit.length) { @@ -366,7 +382,7 @@ namespace ts { emitSkipped = true; } - diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + diagnostics.push(...emitOutput.diagnostics); sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); // If it emitted more than one source files, just mark all those source files as seen if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { @@ -377,7 +393,7 @@ namespace ts { for (const outputFile of emitOutput.outputFiles) { const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); if (error) { - (diagnostics || (diagnostics = [])).push(error); + diagnostics.push(error); } if (emittedFiles) { emittedFiles.push(outputFile.name); @@ -413,41 +429,43 @@ namespace ts { } // Create new source file if requested or the versions dont match - if (!hostSourceFile) { + if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { changedFilePaths.push(path); - const sourceFile = getSourceFile(fileName, languageVersion, onError); - if (sourceFile) { - sourceFile.version = "0"; - const fileWatcher = watchSourceFileForChanges(sourceFile.path); - sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + + const sourceFile = getNewSourceFile(); + if (hostSourceFile) { + if (shouldCreateNewSourceFile) { + hostSourceFile.version++; + } + if (sourceFile) { + hostSourceFile.sourceFile = sourceFile; + sourceFile.version = hostSourceFile.version.toString(); + if (!hostSourceFile.fileWatcher) { + hostSourceFile.fileWatcher = watchSourceFileForChanges(path); + } + } + else { + // There is no source file on host any more, close the watch, missing file paths will track it + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, hostSourceFile.version.toString()); + } } else { - sourceFilesCache.set(path, "0"); + let fileWatcher: FileWatcher; + if (sourceFile) { + sourceFile.version = "0"; + fileWatcher = watchSourceFileForChanges(path); + sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + } + else { + sourceFilesCache.set(path, "0"); + } } return sourceFile; } - else if (shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { - changedFilePaths.push(path); - if (shouldCreateNewSourceFile) { - hostSourceFile.version++; - } - const newSourceFile = getSourceFile(fileName, languageVersion, onError); - if (newSourceFile) { - newSourceFile.version = hostSourceFile.version.toString(); - hostSourceFile.sourceFile = newSourceFile; - } - else { - // File doesnt exist any more - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, hostSourceFile.version.toString()); - } - - return newSourceFile; - } - return hostSourceFile.sourceFile; - function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { + function getNewSourceFile() { let text: string; try { performance.mark("beforeIORead"); @@ -459,7 +477,6 @@ namespace ts { if (onError) { onError(e.message); } - text = ""; } return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; @@ -496,14 +513,14 @@ namespace ts { // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. function scheduleProgramUpdate() { - if (!sys.setTimeout || !sys.clearTimeout) { + if (!system.setTimeout || !system.clearTimeout) { return; } if (timerToUpdateProgram) { - sys.clearTimeout(timerToUpdateProgram); + system.clearTimeout(timerToUpdateProgram); } - timerToUpdateProgram = sys.setTimeout(updateProgram, 250); + timerToUpdateProgram = system.setTimeout(updateProgram, 250); } function scheduleProgramReload() { @@ -514,7 +531,7 @@ namespace ts { function updateProgram() { timerToUpdateProgram = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), system); if (needsReload) { reloadConfigFile(); @@ -526,8 +543,6 @@ namespace ts { function reloadConfigFile() { writeLog(`Reloading config file: ${configFileName}`); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - needsReload = false; const cachedHost = host as CachedSystem; @@ -550,6 +565,8 @@ namespace ts { function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + + updateCachedSystem(fileName, path); const hostSourceFile = sourceFilesCache.get(path); if (hostSourceFile) { // Update the cache @@ -570,10 +587,18 @@ namespace ts { } } } + // Update the program scheduleProgramUpdate(); } + function updateCachedSystem(fileName: string, path: Path) { + if (configFileName) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(fileName, getDirectoryPath(path)); + (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); + } + } + function watchMissingFilePath(missingFilePath: Path) { return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); } @@ -588,10 +613,7 @@ namespace ts { closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); missingFilesMap.delete(missingFilePath); - if (configFileName) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); - } + updateCachedSystem(filename, missingFilePath); // Delete the entry in the source files cache so that new source file is created removeSourceFile(missingFilePath); @@ -621,10 +643,12 @@ namespace ts { function onFileAddOrRemoveInWatchedDirectory(fileName: string) { Debug.assert(!!configFileName); - (host as CachedSystem).addOrDeleteFileOrFolder(fileName); + + const path = toPath(fileName, currentDirectory, getCanonicalFileName); // Since the file existance changed, update the sourceFiles cache - removeSourceFile(toPath(fileName, currentDirectory, getCanonicalFileName)); + updateCachedSystem(fileName, path); + removeSourceFile(path); // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". @@ -640,7 +664,7 @@ namespace ts { if (!needsReload) { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []); if (!configFileSpecs.filesSpecs) { - reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], /*host*/ undefined); + reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], getDefaultFormatDiagnosticsHost(system)); } rootFileNames = result.fileNames; @@ -662,7 +686,7 @@ namespace ts { } function computeHash(data: string) { - return sys.createHash ? sys.createHash(data) : data; + return system.createHash ? system.createHash(data) : data; } } @@ -718,35 +742,35 @@ namespace ts { } /* @internal */ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: System): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System): ParsedCommandLine { let configFileText: string; try { - configFileText = host.readFile(configFileName); + configFileText = system.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); - host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + reportWatchDiagnostic(error, system); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], /* compilerHost */ undefined); - host.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + reportDiagnostics([error], getDefaultFormatDiagnosticsHost(system)); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } const result = parseJsonText(configFileName, configFileText); - reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + reportDiagnostics(result.parseDiagnostics, getDefaultFormatDiagnosticsHost(system)); - const cwd = host.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); + const cwd = system.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + reportDiagnostics(configParseResult.errors, getDefaultFormatDiagnosticsHost(system)); return configParseResult; } - function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, oldProgram?: Program) { + function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, system: System, oldProgram?: Program) { const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; let statistics: Statistic[]; if (hasDiagnostics) { @@ -759,12 +783,12 @@ namespace ts { if (compilerOptions.listFiles) { forEach(program.getSourceFiles(), file => { - sys.write(file.fileName + sys.newLine); + system.write(file.fileName + system.newLine); }); } if (hasDiagnostics) { - const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; + const memoryUsed = system.getMemoryUsage ? system.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); reportCountStatistic("Nodes", program.getNodeCount()); @@ -830,9 +854,9 @@ namespace ts { } diagnostics = diagnostics.concat(emitOutput.diagnostics); - reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); + reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), getDefaultFormatDiagnosticsHost(system)); - reportEmittedFiles(emitOutput.emittedFiles); + reportEmittedFiles(emitOutput.emittedFiles, system); if (emitOutput.emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; @@ -859,7 +883,7 @@ namespace ts { } for (const { name, value } of statistics) { - sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); + system.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + system.newLine); } } @@ -1012,7 +1036,3 @@ namespace ts { if (ts.Debug.isDebugging) { ts.Debug.enableDebugInfo(); } - -if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { - ts.sys.tryEnableSourceMapsForHost(); -} diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index dfdd0a2fee8..7c3ad0bc5a3 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -93,6 +93,7 @@ "rwcRunner.ts", "test262Runner.ts", "runner.ts", + "virtualFileSystemWithWatch.ts", "../server/protocol.ts", "../server/session.ts", "../server/client.ts", @@ -116,6 +117,7 @@ "./unittests/convertCompilerOptionsFromJson.ts", "./unittests/convertTypeAcquisitionFromJson.ts", "./unittests/tsserverProjectSystem.ts", + "./unittests/tscWatchMode.ts", "./unittests/matchFiles.ts", "./unittests/initializeTSConfig.ts", "./unittests/compileOnSave.ts", diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts new file mode 100644 index 00000000000..630d9a71d20 --- /dev/null +++ b/src/harness/unittests/tscWatchMode.ts @@ -0,0 +1,1000 @@ +/// +/// +/// + +namespace ts.tscWatch { + + export import WatchedSystem = ts.TestFSWithWatch.TestServerHost; + export type TestServerHostCreationParameters = ts.TestFSWithWatch.TestServerHostCreationParameters; + export type File = ts.TestFSWithWatch.File; + export type FileOrFolder = ts.TestFSWithWatch.FileOrFolder; + export type Folder = ts.TestFSWithWatch.Folder; + export type FSEntry = ts.TestFSWithWatch.FSEntry; + export import createWatchedSystem = ts.TestFSWithWatch.createWatchedSystem; + export import checkFileNames = ts.TestFSWithWatch.checkFileNames; + export import libFile = ts.TestFSWithWatch.libFile; + export import checkWatchedFiles = ts.TestFSWithWatch.checkWatchedFiles; + export import checkWatchedDirectories = ts.TestFSWithWatch.checkWatchedDirectories; + export import checkOutputContains = ts.TestFSWithWatch.checkOutputContains; + export import checkOutputDoesNotContain = ts.TestFSWithWatch.checkOutputDoesNotContain; + + export function checkProgramActualFiles(program: Program, expectedFiles: string[]) { + checkFileNames(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles); + } + + export function checkProgramRootFiles(program: Program, expectedFiles: string[]) { + checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); + } + + export function createWatchWithConfig(configFilePath: string, host: WatchedSystem) { + const configFileResult = parseConfigFile(configFilePath, {}, host); + return createWatchModeWithConfigFile(configFileResult, {}, host); + } + + describe("tsc-watch program updates", () => { + const commonFile1: FileOrFolder = { + path: "/a/b/commonFile1.ts", + content: "let x = 1" + }; + const commonFile2: FileOrFolder = { + path: "/a/b/commonFile2.ts", + content: "let y = 1" + }; + + it("create watch without config file", () => { + const appFile: FileOrFolder = { + path: "/a/b/c/app.ts", + content: ` + import {f} from "./module" + console.log(f) + ` + }; + + const moduleFile: FileOrFolder = { + path: "/a/b/c/module.d.ts", + content: `export let x: number` + }; + const host = createWatchedSystem([appFile, moduleFile, libFile]); + const watch = createWatchModeWithoutConfigFile([appFile.path], {}, host); + + checkProgramActualFiles(watch(), [appFile.path, libFile.path, moduleFile.path]); + + // TODO: Should we watch creation of config files in the root file's file hierarchy? + + // const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; + // const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); + // checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + }); + + it("can handle tsconfig file name with difference casing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: ["app.ts"] + }) + }; + + const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); + const watch = createWatchWithConfig(upperCaseConfigFilePath, host); + checkProgramActualFiles(watch(), [f1.path]); + }); + + it("create configured project without file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: ` + { + "compilerOptions": {}, + "exclude": [ + "e" + ] + }` + }; + const file1: FileOrFolder = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: FileOrFolder = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: FileOrFolder = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; + + const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); + const configFileResult = parseConfigFile(configFile.path, {}, host); + assert.equal(configFileResult.errors.length, 0, `expect no errors in config file, got ${JSON.stringify(configFileResult.errors)}`); + + const watch = createWatchModeWithConfigFile(configFileResult, {}, host); + + checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]); + checkProgramRootFiles(watch(), [file1.path, file2.path]); + checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); + checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); + }); + + // TODO: if watching for config file creation + // it("add and then remove a config file in a folder with loose files", () => { + // }); + + it("add new files to a configured program without file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createWatchedSystem([commonFile1, libFile, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); + + checkProgramRootFiles(watch(), [commonFile1.path]); + + // add a new ts file + host.reloadFS([commonFile1, commonFile2, libFile, configFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + }); + + it("should ignore non-existing files specified in the config file", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "files": [ + "commonFile1.ts", + "commonFile3.ts" + ] + }` + }; + const host = createWatchedSystem([commonFile1, commonFile2, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + + const commonFile3 = "/a/b/commonFile3.ts"; + checkProgramRootFiles(watch(), [commonFile1.path, commonFile3]); + checkProgramActualFiles(watch(), [commonFile1.path]); + }); + + it("handle recreated files correctly", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createWatchedSystem([commonFile1, commonFile2, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + + // delete commonFile2 + host.reloadFS([commonFile1, configFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [commonFile1.path]); + + // re-add commonFile2 + host.reloadFS([commonFile1, commonFile2, configFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + }); + + it("handles the missing files - that were added to program because they were added with /// { + const file1: FileOrFolder = { + path: "/a/b/commonFile1.ts", + content: `/// + let x = y` + }; + const host = createWatchedSystem([file1, libFile]); + const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, libFile.path]); + const errors = [ + `a/b/commonFile1.ts(1,22): error TS6053: File '${commonFile2.path}' not found.${host.newLine}`, + `a/b/commonFile1.ts(2,29): error TS2304: Cannot find name 'y'.${host.newLine}` + ]; + checkOutputContains(host, errors); + host.clearOutput(); + + host.reloadFS([file1, commonFile2, libFile]); + host.runQueuedTimeoutCallbacks(); + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, libFile.path, commonFile2.path]); + checkOutputDoesNotContain(host, errors); + }); + + it("should reflect change in config file", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "files": ["${commonFile1.path}", "${commonFile2.path}"] + }` + }; + const files = [commonFile1, commonFile2, configFile]; + const host = createWatchedSystem(files); + const watch = createWatchWithConfig(configFile.path, host); + + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + configFile.content = `{ + "compilerOptions": {}, + "files": ["${commonFile1.path}"] + }`; + + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); // reload the configured project + checkProgramRootFiles(watch(), [commonFile1.path]); + }); + + it("files explicitly excluded in config file", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "exclude": ["/a/c"] + }` + }; + const excludedFile1: FileOrFolder = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; + + const host = createWatchedSystem([commonFile1, commonFile2, excludedFile1, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + }); + + it("should properly handle module resolution changes in config file", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: FileOrFolder = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: FileOrFolder = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "moduleResolution": "node" + }, + "files": ["${file1.path}"] + }` + }; + const files = [file1, nodeModuleFile, classicModuleFile, configFile]; + const host = createWatchedSystem(files); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, nodeModuleFile.path]); + + configFile.content = `{ + "compilerOptions": { + "moduleResolution": "classic" + }, + "files": ["${file1.path}"] + }`; + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, classicModuleFile.path]); + }); + + it("should tolerate config file errors and still try to build a project", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6", + "allowAnything": true + }, + "someOtherProperty": {} + }` + }; + const host = createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); + }); + + it("changes in files are reflected in project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createWatchedSystem([file1, file2, file3]); + const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, file2.path]); + + const modifiedFile2 = { + path: file2.path, + content: `export * from "../c/f3"` // now inferred project should inclule file3 + }; + + host.reloadFS([file1, modifiedFile2, file3]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [file1.path]); + checkProgramActualFiles(watch(), [file1.path, modifiedFile2.path, file3.path]); + }); + + it("deleted files affect project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createWatchedSystem([file1, file2, file3]); + const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); + + host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(1); + + checkProgramActualFiles(watch(), [file1.path]); + }); + + it("deleted files affect project structure - 2", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createWatchedSystem([file1, file2, file3]); + const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], {}, host); + checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); + + host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(1); + + checkProgramActualFiles(watch(), [file1.path, file3.path]); + }); + + it("config file includes the file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "export let x = 5" + }; + const file2 = { + path: "/a/c/f2.ts", + content: `import {x} from "../b/f1"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: "export let y = 1" + }; + const configFile = { + path: "/a/c/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + + const host = createWatchedSystem([file1, file2, file3, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + + checkProgramRootFiles(watch(), [file2.path, file3.path]); + checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); + }); + + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` + export * from "../c/f2"; + export * from "../d/f3";` + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = createWatchedSystem([file1, file2, file3]); + const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], {}, host); + checkProgramActualFiles(watch(), [file2.path, file3.path]); + + const watch2 = createWatchModeWithoutConfigFile([file1.path], {}, host); + checkProgramActualFiles(watch2(), [file1.path, file2.path, file3.path]); + + // Previous program shouldnt be updated + checkProgramActualFiles(watch(), [file2.path, file3.path]); + host.checkTimeoutQueueLength(0); + }); + + it("can correctly update configured project when set of root files has changed (new file on disk)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + + const host = createWatchedSystem([file1, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramActualFiles(watch(), [file1.path]); + + host.reloadFS([file1, file2, configFile]); + host.checkTimeoutQueueLengthAndRun(1); + + checkProgramActualFiles(watch(), [file1.path, file2.path]); + checkProgramRootFiles(watch(), [file1.path, file2.path]); + }); + + it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + + const host = createWatchedSystem([file1, file2, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + + checkProgramActualFiles(watch(), [file1.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [file1.path, file2.path]); + checkProgramActualFiles(watch(), [file1.path, file2.path]); + }); + + it("can update configured project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + const host = createWatchedSystem([file1, file2, configFile]); + const watch = createWatchWithConfig(configFile.path, host); + checkProgramActualFiles(watch(), [file1.path, file2.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramRootFiles(watch(), [file1.path, file2.path]); + checkProgramActualFiles(watch(), [file1.path, file2.path]); + }); + + it("config file is deleted", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createWatchedSystem([file1, file2, config]); + const watch = createWatchWithConfig(config.path, host); + + checkProgramActualFiles(watch(), [file1.path, file2.path]); + + host.clearOutput(); + host.reloadFS([file1, file2]); + host.checkTimeoutQueueLengthAndRun(1); + + assert.equal(host.exitCode, ExitStatus.DiagnosticsPresent_OutputsSkipped); + checkOutputContains(host, [`error TS6053: File '${config.path}' not found.${host.newLine}`]); + }); + + it("Proper errors: document is not contained in project", () => { + const file1 = { + path: "/a/b/app.ts", + content: "" + }; + const corruptedConfig = { + path: "/a/b/tsconfig.json", + content: "{" + }; + const host = createWatchedSystem([file1, corruptedConfig]); + const watch = createWatchWithConfig(corruptedConfig.path, host); + + checkProgramActualFiles(watch(), [file1.path]); + }); + + it("correctly handles changes in lib section of config file", () => { + const libES5 = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const libES2015Promise = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + const app = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const config1 = { + path: "/src/tsconfig.json", + content: JSON.stringify( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": true, + "sourceMap": false, + "lib": [ + "es5" + ] + } + }) + }; + const config2 = { + path: config1.path, + content: JSON.stringify( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": true, + "sourceMap": false, + "lib": [ + "es5", + "es2015.promise" + ] + } + }) + }; + const host = createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); + const watch = createWatchWithConfig(config1.path, host); + + checkProgramActualFiles(watch(), [libES5.path, app.path]); + + host.reloadFS([libES5, libES2015Promise, app, config2]); + host.checkTimeoutQueueLengthAndRun(1); + checkProgramActualFiles(watch(), [libES5.path, libES2015Promise.path, app.path]); + }); + + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = createWatchedSystem([f, config]); + const watch = createWatchWithConfig(config.path, host); + checkProgramActualFiles(watch(), [f.path]); + }); + + it("rename a module file and rename back should restore the states for inferred projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createWatchedSystem([moduleFile, file1, libFile]); + createWatchModeWithoutConfigFile([file1.path], {}, host); + const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n"; + checkOutputDoesNotContain(host, [error]); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [error]); + + host.clearOutput(); + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputDoesNotContain(host, [error]); + }); + + it("rename a module file and rename back should restore the states for configured projects", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); + createWatchWithConfig(configFile.path, host); + + const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`; + checkOutputDoesNotContain(host, [error]); + + const moduleFileOldPath = moduleFile.path; + const moduleFileNewPath = "/a/b/moduleFile1.ts"; + moduleFile.path = moduleFileNewPath; + host.reloadFS([moduleFile, file1, configFile, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [error]); + + host.clearOutput(); + moduleFile.path = moduleFileOldPath; + host.reloadFS([moduleFile, file1, configFile, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputDoesNotContain(host, [error]); + }); + + it("types should load from config file path if config exists", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) + }; + const node = { + path: "/a/b/node_modules/@types/node/index.d.ts", + content: "declare var process: any" + }; + const cwd = { + path: "/a/c" + }; + const host = createWatchedSystem([f1, config, node, cwd], { currentDirectory: cwd.path }); + const watch = createWatchWithConfig(config.path, host); + + checkProgramActualFiles(watch(), [f1.path, node.path]); + }); + + it("add the missing module file for inferred project: should remove the `module not found` error", () => { + const moduleFile = { + path: "/a/b/moduleFile.ts", + content: "export function bar() { };" + }; + const file1 = { + path: "/a/b/file1.ts", + content: "import * as T from './moduleFile'; T.bar();" + }; + const host = createWatchedSystem([file1, libFile]); + createWatchModeWithoutConfigFile([file1.path], {}, host); + + const error = `a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.${host.newLine}`; + checkOutputContains(host, [error]); + host.clearOutput(); + + host.reloadFS([file1, moduleFile, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputDoesNotContain(host, [error]); + }); + + it("Configure file diagnostics events are generated when the config file has errors", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "foo": "bar", + "allowJS": true + } + }` + }; + + const host = createWatchedSystem([file, configFile, libFile]); + createWatchWithConfig(configFile.path, host); + checkOutputContains(host, [ + `a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`, + `a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}` + ]); + }); + + it("Configure file diagnostics events are generated when the config file doesn't have errors", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {} + }` + }; + + const host = createWatchedSystem([file, configFile, libFile]); + createWatchWithConfig(configFile.path, host); + checkOutputDoesNotContain(host, [ + `a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`, + `a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}` + ]); + }); + + it("Configure file diagnostics events are generated when the config file changes", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {} + }` + }; + + const host = createWatchedSystem([file, configFile, libFile]); + createWatchWithConfig(configFile.path, host); + const error = `a/b/tsconfig.json(3,25): error TS5023: Unknown compiler option 'haha'.${host.newLine}`; + checkOutputDoesNotContain(host, [error]); + + configFile.content = `{ + "compilerOptions": { + "haha": 123 + } + }`; + host.reloadFS([file, configFile, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [error]); + + host.clearOutput(); + configFile.content = `{ + "compilerOptions": {} + }`; + host.reloadFS([file, configFile, libFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputDoesNotContain(host, [error]); + }); + + it("non-existing directories listed in config file input array should be tolerated without crashing the server", () => { + const configFile = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": {}, + "include": ["app/*", "test/**/*", "something"] + }` + }; + const file1 = { + path: "/a/b/file1.ts", + content: "let t = 10;" + }; + + const host = createWatchedSystem([file1, configFile, libFile]); + const watch = createWatchWithConfig(configFile.path, host); + + checkProgramActualFiles(watch(), [libFile.path]); + }); + + it("non-existing directories listed in config file input array should be able to handle @types if input file list is empty", () => { + const f = { + path: "/a/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compiler: {}, + files: [] + }) + }; + const t1 = { + path: "/a/node_modules/@types/typings/index.d.ts", + content: `export * from "./lib"` + }; + const t2 = { + path: "/a/node_modules/@types/typings/lib.d.ts", + content: `export const x: number` + }; + const host = createWatchedSystem([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); + const watch = createWatchWithConfig(config.path, host); + + checkProgramActualFiles(watch(), [t1.path, t2.path]); + }); + + it("should support files without extensions", () => { + const f = { + path: "/a/compile", + content: "let x = 1" + }; + const host = createWatchedSystem([f, libFile]); + const watch = createWatchModeWithoutConfigFile([f.path], { allowNonTsExtensions: true }, host); + checkProgramActualFiles(watch(), [f.path, libFile.path]); + }); + + it("Options Diagnostic locations reported correctly with changes in configFile contents when options change", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFileContentBeforeComment = `{`; + const configFileContentComment = ` + // comment + // More comment`; + const configFileContentAfterComment = ` + "compilerOptions": { + "allowJs": true, + "declaration": true + } + }`; + const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; + const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; + + const line = 5; + const errors = (line: number) => [ + `a/b/tsconfig.json(${line},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n`, + `a/b/tsconfig.json(${line + 1},25): error TS5053: Option \'allowJs\' cannot be specified with option \'declaration\'.\n` + ]; + + const configFile = { + path: "/a/b/tsconfig.json", + content: configFileContentWithComment + }; + + const host = createWatchedSystem([file, libFile, configFile]); + createWatchWithConfig(configFile.path, host); + checkOutputContains(host, errors(line)); + checkOutputDoesNotContain(host, errors(line - 2)); + host.clearOutput(); + + configFile.content = configFileContentWithoutCommentLine; + host.reloadFS([file, configFile]); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, errors(line - 2)); + checkOutputDoesNotContain(host, errors(line)); + }); + }); + + describe("tsc-watch emit", () => { + it("emit with outFile or out setting projectUsesOutFile should not be returned if not set", () => { + const f1 = { + path: "/a/a.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/b.ts", + content: "let y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { listEmittedFiles: true } + }) + }; + const files = [f1, f2, config, libFile]; + const host = createWatchedSystem([f1, f2, config, libFile]); + createWatchWithConfig(config.path, host); + const emittedF1 = `TSFILE: ${f1.path.replace(".ts", ".js")}${host.newLine}`; + const emittedF2 = `TSFILE: ${f2.path.replace(".ts", ".js")}${host.newLine}`; + checkOutputContains(host, [emittedF1, emittedF2]); + host.clearOutput(); + + f1.content = "let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [emittedF1]); + checkOutputDoesNotContain(host, [emittedF2]); + }); + + it("emit with outFile or out setting projectUsesOutFile should be true if out is set", () => { + const outJS = "/a/out.js"; + const f1 = { + path: "/a/a.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/b.ts", + content: "let y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { listEmittedFiles: true, out: outJS } + }) + }; + const files = [f1, f2, config, libFile]; + const host = createWatchedSystem([f1, f2, config, libFile]); + createWatchWithConfig(config.path, host); + const emittedF1 = `TSFILE: ${outJS}${host.newLine}`; + checkOutputContains(host, [emittedF1]); + host.clearOutput(); + + f1.content = "let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [emittedF1]); + }); + + it("emit with outFile or out setting projectUsesOutFile should be true if outFile is set", () => { + const outJs = "/a/out.js"; + const f1 = { + path: "/a/a.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/b.ts", + content: "let y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { listEmittedFiles: true, outFile: outJs } + }) + }; + const files = [f1, f2, config, libFile]; + const host = createWatchedSystem([f1, f2, config, libFile]); + createWatchWithConfig(config.path, host); + const emittedF1 = `TSFILE: ${outJs}${host.newLine}`; + checkOutputContains(host, [emittedF1]); + host.clearOutput(); + + f1.content = "let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + checkOutputContains(host, [emittedF1]); + }); + }); +} diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index f37d685851c..45c0554bd9c 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -30,6 +30,19 @@ namespace ts.TestFSWithWatch { newLine?: string; } + export function createWatchedSystem(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost { + if (!params) { + params = {}; + } + const host = new TestServerHost(/*withSafelist*/ false, + params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, + params.executingFilePath || getExecutingFilePathFromLibFile(), + params.currentDirectory || "/", + fileOrFolderList, + params.newLine); + return host; + } + export function createServerHost(fileOrFolderList: FileOrFolder[], params?: TestServerHostCreationParameters): TestServerHost { if (!params) { params = {}; @@ -112,6 +125,23 @@ namespace ts.TestFSWithWatch { checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); } + export function checkOutputContains(host: TestServerHost, expected: string[] | ReadonlyArray) { + const mapExpected = arrayToSet(expected); + for (const f of host.getOutput()) { + if (mapExpected.has(f)) { + mapExpected.delete(f); + } + } + assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`); + } + + export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | ReadonlyArray) { + const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent); + for (const f of host.getOutput()) { + assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`); + } + } + export class Callbacks { private map: TimeOutCallback[] = []; private nextId = 1; @@ -172,7 +202,7 @@ namespace ts.TestFSWithWatch { this.reloadFS(fileOrFolderList); } - private toFullPath(s: string) { + toFullPath(s: string) { const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); return this.toPath(fullPath); } @@ -345,6 +375,11 @@ namespace ts.TestFSWithWatch { return isFile(this.fs.get(path)); } + readFile(s: string) { + const fsEntry = this.fs.get(this.toFullPath(s)); + return isFile(fsEntry) ? fsEntry.content : undefined; + } + getFileSize(s: string) { const path = this.toFullPath(s); const entry = this.fs.get(path); @@ -432,7 +467,15 @@ namespace ts.TestFSWithWatch { } runQueuedTimeoutCallbacks() { - this.timeoutCallbacks.invoke(); + try { + this.timeoutCallbacks.invoke(); + } + catch (e) { + if (e.message === this.existMessage) { + return; + } + throw e; + } } runQueuedImmediateCallbacks() { @@ -482,11 +525,15 @@ namespace ts.TestFSWithWatch { clear(this.output); } - readonly readFile = (s: string) => (this.fs.get(this.toFullPath(s))).content; + readonly existMessage = "System Exit"; + exitCode: number; readonly resolvePath = (s: string) => s; readonly getExecutingFilePath = () => this.executingFilePath; readonly getCurrentDirectory = () => this.currentDirectory; - readonly exit = notImplemented; + exit(exitCode?: number) { + this.exitCode = exitCode; + throw new Error(this.existMessage); + } readonly getEnvironmentVariable = notImplemented; } } diff --git a/src/services/services.ts b/src/services/services.ts index 091364b5c50..8fe158a4798 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1116,7 +1116,7 @@ namespace ts { let hostCache = new HostCache(host, getCanonicalFileName); const rootFileNames = hostCache.getRootFileNames(); // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), (path) => hostCache.getVersion(path))) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists)) { return; } @@ -1139,14 +1139,7 @@ namespace ts { getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: noop, getCurrentDirectory: () => currentDirectory, - fileExists: (fileName): boolean => { - // stub missing host functionality - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const entry = hostCache.getEntryByPath(path); - return entry ? - !isString(entry) : - (host.fileExists && host.fileExists(fileName)); - }, + fileExists, readFile(fileName) { // stub missing host functionality const path = toPath(fileName, currentDirectory, getCanonicalFileName); @@ -1189,6 +1182,14 @@ namespace ts { program.getTypeChecker(); return; + function fileExists(fileName: string) { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const entry = hostCache.getEntryByPath(path); + return entry ? + !isString(entry) : + (host.fileExists && host.fileExists(fileName)); + } + // Release any files we have acquired in the old program but are // not part of the new program. function onReleaseOldSourceFile(oldSourceFile: SourceFile, oldOptions: CompilerOptions) { From 2dd6aed654f4dc8f0a5bba326ef6764030a12683 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jul 2017 19:34:20 -0700 Subject: [PATCH 040/144] Emit tests --- src/compiler/builder.ts | 20 +- src/harness/unittests/tscWatchMode.ts | 510 +++++++++++++++++++--- src/harness/virtualFileSystemWithWatch.ts | 5 +- 3 files changed, 460 insertions(+), 75 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 5661270e8f7..c507b1570a8 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -294,9 +294,11 @@ namespace ts { existingMap = mutateExistingMapWithNewSet( existingMap, getReferencedFiles(program, sourceFile), - // Creating new Reference: Also add referenced by + // Creating new Reference: as sourceFile references file with path 'key' + // in other words source file (path) is referenced by 'key' key => { referencedBy.add(key, path); return true; }, - // Remove existing reference + // Remove existing reference by entry: source file doesnt reference file 'key' any more + // in other words source file (path) is not referenced by 'key' (key, _existingValue) => { referencedBy.remove(key, path); } ); references.set(path, existingMap); @@ -338,28 +340,28 @@ namespace ts { // Because if so, its own referencedBy files need to be saved as well to make the // emitting result consistent with files on disk. - const fileNamesMap = createMap(); - const setFileName = (path: Path, sourceFile: SourceFile) => { - fileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined); + const seenFileNamesMap = createMap(); + const setSeenFileName = (path: Path, sourceFile: SourceFile) => { + seenFileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined); }; // Start with the paths this file was referenced by const path = sourceFile.path; - setFileName(path, sourceFile); + setSeenFileName(path, sourceFile); const queue = getReferencedByPaths(path).slice(); while (queue.length > 0) { const currentPath = queue.pop(); - if (!fileNamesMap.has(currentPath)) { + if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = program.getSourceFileByPath(currentPath); if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { queue.push(...getReferencedByPaths(currentPath)); } - setFileName(currentPath, currentSourceFile); + setSeenFileName(currentPath, currentSourceFile); } } // Return array of values that needs emit - return flatMapIter(fileNamesMap.values(), value => value); + return flatMapIter(seenFileNamesMap.values(), value => value); } } } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 630d9a71d20..1025e250d27 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -31,6 +31,45 @@ namespace ts.tscWatch { return createWatchModeWithConfigFile(configFileResult, {}, host); } + function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { + return `TSFILE: ${file.path.replace(".ts", ".js")}${host.newLine}`; + } + + function getEmittedLineForSingleFileOutput(filename: string, host: WatchedSystem) { + return `TSFILE: ${filename}${host.newLine}`; + } + + interface FileOrFolderEmit extends FileOrFolder { + output?: string; + } + + function getFileOrFolderEmit(file: FileOrFolder, getOutput?: (file: FileOrFolder) => string): FileOrFolderEmit { + const result = file as FileOrFolderEmit; + if (getOutput) { + result.output = getOutput(file); + } + return result; + } + + function getEmittedLines(files: FileOrFolderEmit[]) { + const seen = createMap(); + const result: string[] = []; + for (const { output} of files) { + if (output && !seen.has(output)) { + seen.set(output, true); + result.push(output); + } + } + return result; + } + + function checkAffectedLines(host: WatchedSystem, affectedFiles: FileOrFolderEmit[], allEmittedFiles: string[]) { + const expectedAffectedFiles = getEmittedLines(affectedFiles); + const expectedNonAffectedFiles = mapDefined(allEmittedFiles, line => contains(expectedAffectedFiles, line) ? undefined : line); + checkOutputContains(host, expectedAffectedFiles); + checkOutputDoesNotContain(host, expectedNonAffectedFiles); + } + describe("tsc-watch program updates", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -908,93 +947,434 @@ namespace ts.tscWatch { }); }); - describe("tsc-watch emit", () => { - it("emit with outFile or out setting projectUsesOutFile should not be returned if not set", () => { - const f1 = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/b.ts", - content: "let y = 1" - }; - const config = { + describe("tsc-watch emit with outFile or out setting", () => { + function createWatchForOut(out?: string, outFile?: string) { + const host = createWatchedSystem([]); + const config: FileOrFolderEmit = { path: "/a/tsconfig.json", content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } }) }; - const files = [f1, f2, config, libFile]; - const host = createWatchedSystem([f1, f2, config, libFile]); - createWatchWithConfig(config.path, host); - const emittedF1 = `TSFILE: ${f1.path.replace(".ts", ".js")}${host.newLine}`; - const emittedF2 = `TSFILE: ${f2.path.replace(".ts", ".js")}${host.newLine}`; - checkOutputContains(host, [emittedF1, emittedF2]); - host.clearOutput(); - f1.content = "let x = 11"; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkOutputContains(host, [emittedF1]); - checkOutputDoesNotContain(host, [emittedF2]); - }); + let getOutput: (file: FileOrFolder) => string; + if (out) { + config.content = JSON.stringify({ + compilerOptions: { listEmittedFiles: true, out } + }); + getOutput = __ => getEmittedLineForSingleFileOutput(out, host); + } + else if (outFile) { + config.content = JSON.stringify({ + compilerOptions: { listEmittedFiles: true, outFile } + }); + getOutput = __ => getEmittedLineForSingleFileOutput(outFile, host); + } + else { + getOutput = file => getEmittedLineForMultiFileOutput(file, host); + } - it("emit with outFile or out setting projectUsesOutFile should be true if out is set", () => { - const outJS = "/a/out.js"; - const f1 = { + const f1 = getFileOrFolderEmit({ path: "/a/a.ts", content: "let x = 1" - }; - const f2 = { + }, getOutput); + const f2 = getFileOrFolderEmit({ path: "/a/b.ts", content: "let y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { listEmittedFiles: true, out: outJS } - }) - }; + }, getOutput); + const files = [f1, f2, config, libFile]; - const host = createWatchedSystem([f1, f2, config, libFile]); + host.reloadFS(files); createWatchWithConfig(config.path, host); - const emittedF1 = `TSFILE: ${outJS}${host.newLine}`; - checkOutputContains(host, [emittedF1]); + + const allEmittedLines = getEmittedLines(files); + checkOutputContains(host, allEmittedLines); host.clearOutput(); f1.content = "let x = 11"; host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - checkOutputContains(host, [emittedF1]); + checkAffectedLines(host, [f1], allEmittedLines); + } + + it("projectUsesOutFile should not be returned if not set", () => { + createWatchForOut(); }); - it("emit with outFile or out setting projectUsesOutFile should be true if outFile is set", () => { + it("projectUsesOutFile should be true if out is set", () => { const outJs = "/a/out.js"; - const f1 = { - path: "/a/a.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/b.ts", - content: "let y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { listEmittedFiles: true, outFile: outJs } - }) - }; - const files = [f1, f2, config, libFile]; - const host = createWatchedSystem([f1, f2, config, libFile]); - createWatchWithConfig(config.path, host); - const emittedF1 = `TSFILE: ${outJs}${host.newLine}`; - checkOutputContains(host, [emittedF1]); - host.clearOutput(); + createWatchForOut(outJs); + }); - f1.content = "let x = 11"; - host.reloadFS(files); - host.runQueuedTimeoutCallbacks(); - checkOutputContains(host, [emittedF1]); + it("projectUsesOutFile should be true if outFile is set", () => { + const outJs = "/a/out.js"; + createWatchForOut(/*out*/ undefined, outJs); }); }); + + describe("tsc-watch emit for configured projects", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + function getInitialState(configObj: any = {}, fileNames?: string[]) { + const host = createWatchedSystem([]); + const getOutputName = (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host); + const moduleFile1 = getFileOrFolderEmit({ + path: moduleFile1Path, + content: "export function Foo() { };", + }, getOutputName); + + const file1Consumer1 = getFileOrFolderEmit({ + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }, getOutputName); + + const file1Consumer2 = getFileOrFolderEmit({ + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }, getOutputName); + + const moduleFile2 = getFileOrFolderEmit({ + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }, getOutputName); + + const globalFile3 = getFileOrFolderEmit({ + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }); + + (configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true; + const configFile = getFileOrFolderEmit({ + path: "/a/b/tsconfig.json", + content: JSON.stringify(configObj) + }); + + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]; + let allEmittedFiles = getEmittedLines(files); + host.reloadFS(files); + + // Initial compile + createWatchWithConfig(configFile.path, host); + if (fileNames) { + checkAffectedLines(host, map(fileNames, name => find(files, file => file.path === name)), allEmittedFiles); + } + else { + checkOutputContains(host, allEmittedFiles); + } + host.clearOutput(); + + return { + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + files, + verifyAffectedFiles, + verifyAffectedAllFiles, + getOutputName + }; + + function verifyAffectedAllFiles() { + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputContains(host, allEmittedFiles); + host.clearOutput(); + } + + function verifyAffectedFiles(expected: FileOrFolderEmit[], filesToReload?: FileOrFolderEmit[]) { + if (!filesToReload) { + filesToReload = files; + } + else if (filesToReload.length > files.length) { + allEmittedFiles = getEmittedLines(filesToReload); + } + host.reloadFS(filesToReload); + host.checkTimeoutQueueLengthAndRun(1); + checkAffectedLines(host, expected, allEmittedFiles); + host.clearOutput(); + } + } + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + verifyAffectedFiles + } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; + verifyAffectedFiles([moduleFile1]); + }); + + it("should be up-to-date with the reference map changes", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + verifyAffectedFiles + } = getInitialState(); + + // Change file1Consumer1 content to `export let y = Foo();` + file1Consumer1.content = `export let y = Foo();`; + verifyAffectedFiles([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer2]); + + // Add the import statements back to file1Consumer1 + file1Consumer1.content = `import {Foo} from "./moduleFile1";let y = Foo();`; + verifyAffectedFiles([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer2, file1Consumer1]); + + // Multiple file edits in one go: + + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + file1Consumer1.content = `export let y = Foo();`; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + }); + + it("should be up-to-date with deleted files", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + files, + verifyAffectedFiles + } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + + // Delete file1Consumer2 + const filesToLoad = mapDefined(files, file => file === file1Consumer2 ? undefined : file); + verifyAffectedFiles([moduleFile1, file1Consumer1], filesToLoad); + }); + + it("should be up-to-date with newly created files", () => { + const { + moduleFile1, file1Consumer1, file1Consumer2, + files, + verifyAffectedFiles, + getOutputName + } = getInitialState(); + + const file1Consumer3 = getFileOrFolderEmit({ + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }, getOutputName); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); + }); + + it("should detect changes in non-root files", () => { + const { + moduleFile1, file1Consumer1, + verifyAffectedFiles, + } = getInitialState({ files: [file1Consumer1Path] }, [file1Consumer1Path, moduleFile1Path]); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1]); + + // change file1 internal, and verify only file1 is affected + moduleFile1.content += "var T1: number;"; + verifyAffectedFiles([moduleFile1]); + }); + + it("should return all files if a global file changed shape", () => { + const { + globalFile3, verifyAffectedAllFiles + } = getInitialState(); + + globalFile3.content += "var T2: string;"; + verifyAffectedAllFiles(); + }); + + //it("should save with base tsconfig.json", () => { + // configFile = { + // path: "/a/b/tsconfig.json", + // content: `{ + // "extends": "/a/tsconfig.json" + // }` + // }; + + // const configFile2: FileOrFolder = { + // path: "/a/tsconfig.json", + // content: `{ + // "compileOnSave": true + // }` + // }; + + // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); + // const typingsInstaller = createTestTypingsInstaller(host); + // const session = createSession(host, typingsInstaller); + + // openFilesForSession([moduleFile1, file1Consumer1], session); + // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); + //}); + + //it("should always return the file itself if '--isolatedModules' is specified", () => { + // configFile = { + // path: "/a/b/tsconfig.json", + // content: `{ + // "compileOnSave": true, + // "compilerOptions": { + // "isolatedModules": true + // } + // }` + // }; + + // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + // const typingsInstaller = createTestTypingsInstaller(host); + // const session = createSession(host, typingsInstaller); + // openFilesForSession([moduleFile1], session); + + // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { + // file: moduleFile1.path, + // line: 1, + // offset: 27, + // endLine: 1, + // endOffset: 27, + // insertString: `Point,` + // }); + // session.executeCommand(file1ChangeShapeRequest); + // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + //}); + + //it("should always return the file itself if '--out' or '--outFile' is specified", () => { + // configFile = { + // path: "/a/b/tsconfig.json", + // content: `{ + // "compileOnSave": true, + // "compilerOptions": { + // "module": "system", + // "outFile": "/a/b/out.js" + // } + // }` + // }; + + // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); + // const typingsInstaller = createTestTypingsInstaller(host); + // const session = createSession(host, typingsInstaller); + // openFilesForSession([moduleFile1], session); + + // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { + // file: moduleFile1.path, + // line: 1, + // offset: 27, + // endLine: 1, + // endOffset: 27, + // insertString: `Point,` + // }); + // session.executeCommand(file1ChangeShapeRequest); + // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); + //}); + + //it("should return cascaded affected file list", () => { + // const file1Consumer1Consumer1: FileOrFolder = { + // path: "/a/b/file1Consumer1Consumer1.ts", + // content: `import {y} from "./file1Consumer1";` + // }; + // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); + // const typingsInstaller = createTestTypingsInstaller(host); + // const session = createSession(host, typingsInstaller); + + // openFilesForSession([moduleFile1, file1Consumer1], session); + // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + + // const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { + // file: file1Consumer1.path, + // line: 2, + // offset: 1, + // endLine: 2, + // endOffset: 1, + // insertString: `export var T: number;` + // }); + // session.executeCommand(changeModuleFile1ShapeRequest1); + // session.executeCommand(changeFile1Consumer1ShapeRequest); + // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + //}); + + //it("should work fine for files with circular references", () => { + // const file1: FileOrFolder = { + // path: "/a/b/file1.ts", + // content: ` + // /// + // export var t1 = 10;` + // }; + // const file2: FileOrFolder = { + // path: "/a/b/file2.ts", + // content: ` + // /// + // export var t2 = 10;` + // }; + // const host = createServerHost([file1, file2, configFile]); + // const typingsInstaller = createTestTypingsInstaller(host); + // const session = createSession(host, typingsInstaller); + + // openFilesForSession([file1, file2], session); + // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); + //}); + + //it("should return results for all projects if not specifying projectFileName", () => { + // const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" }; + // const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; + // const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; + // const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; + // const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; + + // const host = createServerHost([file1, file2, file3, configFile1, configFile2]); + // const session = createSession(host); + + // openFilesForSession([file1, file2, file3], session); + // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); + + // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ + // { projectFileName: configFile1.path, files: [file1, file2] }, + // { projectFileName: configFile2.path, files: [file1, file3] } + // ]); + //}); + + //it("should detect removed code file", () => { + // const referenceFile1: FileOrFolder = { + // path: "/a/b/referenceFile1.ts", + // content: ` + // /// + // export var x = Foo();` + // }; + // const host = createServerHost([moduleFile1, referenceFile1, configFile]); + // const session = createSession(host); + + // openFilesForSession([referenceFile1], session); + // host.reloadFS([referenceFile1, configFile]); + + // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + // sendAffectedFileRequestAndCheckResult(session, request, [ + // { projectFileName: configFile.path, files: [referenceFile1] } + // ]); + // const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); + // sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); + //}); + + //it("should detect non-existing code file", () => { + // const referenceFile1: FileOrFolder = { + // path: "/a/b/referenceFile1.ts", + // content: ` + // /// + // export var x = Foo();` + // }; + // const host = createServerHost([referenceFile1, configFile]); + // const session = createSession(host); + + // openFilesForSession([referenceFile1], session); + // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); + // sendAffectedFileRequestAndCheckResult(session, request, [ + // { projectFileName: configFile.path, files: [referenceFile1] } + // ]); + //}); + }); + } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 45c0554bd9c..95ef3266fb1 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -125,11 +125,14 @@ namespace ts.TestFSWithWatch { checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); } - export function checkOutputContains(host: TestServerHost, expected: string[] | ReadonlyArray) { + export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray) { const mapExpected = arrayToSet(expected); + const mapSeen = createMap(); for (const f of host.getOutput()) { + assert.isUndefined(mapSeen.get(f), `Already found ${f} in ${JSON.stringify(host.getOutput())}`); if (mapExpected.has(f)) { mapExpected.delete(f); + mapSeen.set(f, true); } } assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(flatMapIter(mapExpected.keys(), key => key))} in ${JSON.stringify(host.getOutput())}`); From 89c61e797c794515a06a3719ca95e37d97783d6e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Aug 2017 00:27:46 -0700 Subject: [PATCH 041/144] Modify the api in builder so that it tracks changed files --- src/compiler/builder.ts | 186 +++++++++------ src/compiler/core.ts | 8 +- src/compiler/tscLib.ts | 74 +++--- src/compiler/utilities.ts | 20 +- src/harness/unittests/tscWatchMode.ts | 322 ++++++++++++-------------- src/server/project.ts | 8 +- src/services/services.ts | 4 +- src/services/types.ts | 2 +- 8 files changed, 320 insertions(+), 304 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c507b1570a8..489111f26db 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -18,13 +18,14 @@ namespace ts { text: string; } - export interface Builder { + export interface Builder { /** * This is the callback when file infos in the builder are updated */ onProgramUpdateGraph(program: Program): void; getFilesAffectedBy(program: Program, path: Path): string[]; - emitFile(program: Program, path: Path): T | EmitOutput; + emitFile(program: Program, path: Path): EmitOutput; + emitChangedFiles(program: Program): EmitOutputDetailed[]; clear(): void; } @@ -38,19 +39,7 @@ namespace ts { getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[]; } - export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed { - return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles, - cancellationToken, customTransformers) as EmitOutputDetailed; - } - - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { - return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles, - cancellationToken, customTransformers); - } - - function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed { const outputFiles: OutputFile[] = []; let emittedSourceFiles: SourceFile[]; @@ -75,20 +64,23 @@ namespace ts { } } - export function createBuilder( + export function createBuilder( getCanonicalFileName: (fileName: string) => string, - getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T, + getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed, computeHash: (data: string) => string, shouldEmitFile: (sourceFile: SourceFile) => boolean - ): Builder { + ): Builder { let isModuleEmit: boolean | undefined; // Last checked shape signature for the file info - let fileInfos: Map; + type FileInfo = { version: string; signature: string; }; + let fileInfos: Map; + let changedFilesSinceLastEmit: Map; let emitHandler: EmitHandler; return { onProgramUpdateGraph, getFilesAffectedBy, emitFile, + emitChangedFiles, clear }; @@ -100,19 +92,37 @@ namespace ts { fileInfos = undefined; } - fileInfos = mutateExistingMap( + changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap(); + fileInfos = mutateExistingMapWithSameExistingValues( fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), - (_path, sourceFile) => { - emitHandler.addScriptInfo(program, sourceFile); - return ""; - }, - (path: Path, _value) => emitHandler.removeScriptInfo(path), - /*isSameValue*/ undefined, - /*OnDeleteExistingMismatchValue*/ undefined, - (_prevValue, sourceFile) => emitHandler.updateScriptInfo(program, sourceFile) + // Add new file info + (_path, sourceFile) => addNewFileInfo(program, sourceFile), + // Remove existing file info + removeExistingFileInfo, + // We will update in place instead of deleting existing value and adding new one + (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) ); } + function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { + changedFilesSinceLastEmit.set(sourceFile.path, true); + emitHandler.addScriptInfo(program, sourceFile); + return { version: sourceFile.version, signature: undefined }; + } + + function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) { + changedFilesSinceLastEmit.set(path, true); + emitHandler.removeScriptInfo(path); + } + + function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { + if (existingInfo.version !== sourceFile.version) { + changedFilesSinceLastEmit.set(sourceFile.path, true); + existingInfo.version = sourceFile.version; + emitHandler.updateScriptInfo(program, sourceFile); + } + } + function ensureProgramGraph(program: Program) { if (!emitHandler) { createProgramGraph(program); @@ -130,20 +140,53 @@ namespace ts { const sourceFile = program.getSourceFile(path); const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : []; - if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(program, sourceFile)) { + const info = fileInfos && fileInfos.get(path); + if (!info || !updateShapeSignature(program, sourceFile, info)) { return singleFileResult; } + Debug.assert(!!sourceFile); return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult); } - function emitFile(program: Program, path: Path): T | EmitOutput { + function emitFile(program: Program, path: Path) { ensureProgramGraph(program); if (!fileInfos || !fileInfos.has(path)) { return { outputFiles: [], emitSkipped: true }; } - return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false); + return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false); + } + + function emitChangedFiles(program: Program): EmitOutputDetailed[] { + ensureProgramGraph(program); + const result: EmitOutputDetailed[] = []; + if (changedFilesSinceLastEmit) { + const seenFiles = createMap(); + changedFilesSinceLastEmit.forEach((__value, path: Path) => { + const affectedFiles = getFilesAffectedBy(program, path); + for (const file of affectedFiles) { + if (!seenFiles.has(file)) { + const sourceFile = program.getSourceFile(file); + seenFiles.set(file, sourceFile); + if (sourceFile) { + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed; + result.push(emitOutput); + + // mark all the emitted source files as seen + if (emitOutput.emittedSourceFiles) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.fileName, file); + } + } + } + } + } + }); + + changedFilesSinceLastEmit = undefined; + } + return result; } function clear() { @@ -152,10 +195,6 @@ namespace ts { fileInfos = undefined; } - function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) { - return sourceFile && (isExternalModule(sourceFile) || containsOnlyAmbientModules(sourceFile)); - } - /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, @@ -174,20 +213,21 @@ namespace ts { /** * @return {boolean} indicates if the shape signature has changed since last update. */ - function updateShapeSignature(program: Program, sourceFile: SourceFile) { - const path = sourceFile.path; - - const prevSignature = fileInfos.get(path); - let latestSignature = prevSignature; + function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) { + const prevSignature = info.signature; + let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = computeHash(sourceFile.text); - fileInfos.set(path, latestSignature); + info.signature = latestSignature; } else { - const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = computeHash(emitOutput.outputFiles[0].text); - fileInfos.set(path, latestSignature); + info.signature = latestSignature; + } + else { + latestSignature = prevSignature; } } @@ -201,6 +241,7 @@ namespace ts { */ function getReferencedFiles(program: Program, sourceFile: SourceFile): Map { const referencedFiles = createMap(); + // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. // To avoid invernal conversion, the key of the referencedFiles map must be of type Path @@ -281,44 +322,45 @@ namespace ts { function getModuleEmitHandler(): EmitHandler { const references = createMap>(); const referencedBy = createMultiMap(); - const scriptVersionForReferences = createMap(); return { - addScriptInfo, + addScriptInfo: setReferences, removeScriptInfo, - updateScriptInfo, + updateScriptInfo: setReferences, getFilesAffectedByUpdatedShape }; - function setReferences(program: Program, sourceFile: SourceFile, existingMap: Map) { + function setReferences(program: Program, sourceFile: SourceFile) { const path = sourceFile.path; - existingMap = mutateExistingMapWithNewSet( - existingMap, - getReferencedFiles(program, sourceFile), - // Creating new Reference: as sourceFile references file with path 'key' - // in other words source file (path) is referenced by 'key' - key => { referencedBy.add(key, path); return true; }, - // Remove existing reference by entry: source file doesnt reference file 'key' any more - // in other words source file (path) is not referenced by 'key' - (key, _existingValue) => { referencedBy.remove(key, path); } + references.set(path, + mutateExistingMapWithNewSet( + // Existing references + references.get(path), + // Updated references + getReferencedFiles(program, sourceFile), + // Creating new Reference: as sourceFile references file with path 'key' + // in other words source file (path) is referenced by 'key' + key => { referencedBy.add(key, path); return true; }, + // Remove existing reference by entry: source file doesnt reference file 'key' any more + // in other words source file (path) is not referenced by 'key' + (key, _existingValue) => { referencedBy.remove(key, path); } + ) ); - references.set(path, existingMap); - scriptVersionForReferences.set(path, sourceFile.version); - } - - function addScriptInfo(program: Program, sourceFile: SourceFile) { - setReferences(program, sourceFile, undefined); } function removeScriptInfo(path: Path) { + // Remove existing references + references.forEach((_value, key) => { + referencedBy.remove(key, path); + }); references.delete(path); - scriptVersionForReferences.delete(path); - } - function updateScriptInfo(program: Program, sourceFile: SourceFile) { - const path = sourceFile.path; - const lastUpdatedVersion = scriptVersionForReferences.get(path); - if (lastUpdatedVersion !== sourceFile.version) { - setReferences(program, sourceFile, references.get(path)); + // Delete the entry and add files referencing this file, as chagned files too + const referencedByPaths = referencedBy.get(path); + if (referencedByPaths) { + for (const path of referencedByPaths) { + changedFilesSinceLastEmit.set(path, true); + } + referencedBy.delete(path); } } @@ -327,7 +369,7 @@ namespace ts { } function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { - if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) { + if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) { return getAllEmittableFiles(program); } @@ -353,7 +395,7 @@ namespace ts { const currentPath = queue.pop(); if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = program.getSourceFileByPath(currentPath); - if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { + if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) { queue.push(...getReferencedByPaths(currentPath)); } setSeenFileName(currentPath, currentSourceFile); @@ -361,7 +403,7 @@ namespace ts { } // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); + return flatMapIter(seenFileNamesMap.values()); } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cbccdcbed02..2a41427f2fd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -473,12 +473,14 @@ namespace ts { return result; } - export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[] { - const result: U[] = []; + export function flatMapIter(iter: Iterator): T[]; + export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[]; + export function flatMapIter(iter: Iterator, mapfn?: (x: any) => any): any[] { + const result = []; while (true) { const { value, done } = iter.next(); if (done) break; - const res = mapfn(value); + const res = mapfn ? mapfn(value) : value; if (res) { if (isArray(res)) { result.push(...res); diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts index b9234906176..13c1b27a5b0 100644 --- a/src/compiler/tscLib.ts +++ b/src/compiler/tscLib.ts @@ -248,7 +248,6 @@ namespace ts { let timerToUpdateProgram: any; // timer callback to recompile the program const sourceFilesCache = createMap(); // Cache that stores the source file and version info - let changedFilePaths: Path[] = []; let host: System; if (configFileName) { @@ -262,7 +261,7 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true); + const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); if (compilerOptions.pretty) { reportDiagnosticWorker = reportDiagnosticWithColorAndContext; @@ -352,51 +351,44 @@ namespace ts { function emitWithBuilder(program: Program): EmitResult { builder.onProgramUpdateGraph(program); - const filesPendingToEmit = changedFilePaths; - changedFilePaths = []; - - const seenFiles = createMap(); - - let emitSkipped: boolean; - const diagnostics: Diagnostic[] = []; const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; let sourceMaps: SourceMapData[]; - while (filesPendingToEmit.length) { - const filePath = filesPendingToEmit.pop(); - const affectedFiles = builder.getFilesAffectedBy(program, filePath); - for (const file of affectedFiles) { - if (!seenFiles.has(file)) { - seenFiles.set(file, true); - const sourceFile = program.getSourceFile(file); - if (sourceFile) { - writeFiles(builder.emitFile(program, sourceFile.path)); + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + + const result = builder.emitChangedFiles(program); + switch (result.length) { + case 0: + emitSkipped = true; + break; + case 1: + const emitOutput = result[0]; + ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); + writeOutputFiles(emitOutput.outputFiles); + break; + default: + for (const emitOutput of result) { + if (emitOutput.emitSkipped) { + emitSkipped = true; } + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + writeOutputFiles(emitOutput.outputFiles); } - } } - return { emitSkipped, diagnostics, emittedFiles, sourceMaps }; + return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - function writeFiles(emitOutput: EmitOutputDetailed) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - - diagnostics.push(...emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - // If it emitted more than one source files, just mark all those source files as seen - if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.fileName, true); - } - } - for (const outputFile of emitOutput.outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - diagnostics.push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); + function writeOutputFiles(outputFiles: OutputFile[]) { + if (outputFiles) { + for (const outputFile of outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + diagnostics.push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } } } } @@ -430,8 +422,6 @@ namespace ts { // Create new source file if requested or the versions dont match if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { - changedFilePaths.push(path); - const sourceFile = getNewSourceFile(); if (hostSourceFile) { if (shouldCreateNewSourceFile) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 08816382ecf..0a38e6af58b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3656,13 +3656,27 @@ namespace ts { ); } + export function mutateExistingMapWithSameExistingValues( + existingMap: Map, newMap: Map, + createNewValue: (key: string, valueInNewMap: U) => T, + onDeleteExistingValue: (key: string, existingValue: T) => void, + onExistingValue?: (existingValue: T, valueInNewMap: U) => void + ): Map { + return mutateExistingMap( + existingMap, newMap, + createNewValue, onDeleteExistingValue, + /*isSameValue*/ undefined, /*onDeleteExistingMismatchValue*/ undefined, + onExistingValue + ); + } + export function mutateExistingMap( existingMap: Map, newMap: Map, createNewValue: (key: string, valueInNewMap: U) => T, onDeleteExistingValue: (key: string, existingValue: T) => void, isSameValue?: (existingValue: T, valueInNewMap: U) => boolean, OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void, - onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void + onExistingValue?: (existingValue: T, valueInNewMap: U) => void ): Map { // If there are new values update them if (newMap) { @@ -3680,8 +3694,8 @@ namespace ts { existingMap.delete(key); OnDeleteExistingMismatchValue(key, existingValue); } - else if (onSameExistingValue) { - onSameExistingValue(existingValue, valueInNewMap); + else if (onExistingValue) { + onExistingValue(existingValue, valueInNewMap); } }); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 1025e250d27..7622bc2556b 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1015,9 +1015,24 @@ namespace ts.tscWatch { describe("tsc-watch emit for configured projects", () => { const file1Consumer1Path = "/a/b/file1Consumer1.ts"; const moduleFile1Path = "/a/b/moduleFile1.ts"; - function getInitialState(configObj: any = {}, fileNames?: string[]) { + const configFilePath = "/a/b/tsconfig.json"; + type InitialStateParams = { + /** custom config file options */ + configObj?: any; + /** list of the files that will be emitted for first compilation */ + firstCompilationEmitFiles?: string[]; + /** get the emit file for file - default is multi file emit line */ + getEmitLine?(file: FileOrFolder, host: WatchedSystem): string; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): FileOrFolder[]; + /** initial list of files to emit if not the default list */ + firstReloadFileList?: string[]; + }; + function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { const host = createWatchedSystem([]); - const getOutputName = (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host); + const getOutputName = getEmitLine ? (file: FileOrFolder) => getEmitLine(file, host) : + (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host); + const moduleFile1 = getFileOrFolderEmit({ path: moduleFile1Path, content: "export function Foo() { };", @@ -1043,20 +1058,24 @@ namespace ts.tscWatch { content: `interface GlobalFoo { age: number }` }); + const additionalFiles = getAdditionalFileOrFolder ? + map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) : + []; + (configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true; const configFile = getFileOrFolderEmit({ - path: "/a/b/tsconfig.json", + path: configFilePath, content: JSON.stringify(configObj) }); - const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]; + const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles]; let allEmittedFiles = getEmittedLines(files); - host.reloadFS(files); + host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); // Initial compile createWatchWithConfig(configFile.path, host); - if (fileNames) { - checkAffectedLines(host, map(fileNames, name => find(files, file => file.path === name)), allEmittedFiles); + if (firstCompilationEmitFiles) { + checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); } else { checkOutputContains(host, allEmittedFiles); @@ -1066,11 +1085,20 @@ namespace ts.tscWatch { return { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, files, + getFile, verifyAffectedFiles, verifyAffectedAllFiles, getOutputName }; + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } + + function getFile(fileName: string) { + return find(files, file => file.path === fileName); + } + function verifyAffectedAllFiles() { host.reloadFS(files); host.checkTimeoutQueueLengthAndRun(1); @@ -1172,8 +1200,8 @@ namespace ts.tscWatch { it("should detect changes in non-root files", () => { const { moduleFile1, file1Consumer1, - verifyAffectedFiles, - } = getInitialState({ files: [file1Consumer1Path] }, [file1Consumer1Path, moduleFile1Path]); + verifyAffectedFiles + } = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] }); moduleFile1.content = `export var T: number;export function Foo() { };`; verifyAffectedFiles([moduleFile1, file1Consumer1]); @@ -1192,189 +1220,129 @@ namespace ts.tscWatch { verifyAffectedAllFiles(); }); - //it("should save with base tsconfig.json", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "extends": "/a/tsconfig.json" - // }` - // }; + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); - // const configFile2: FileOrFolder = { - // path: "/a/tsconfig.json", - // content: `{ - // "compileOnSave": true - // }` - // }; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); - // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } }, + getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host) + }); - // openFilesForSession([moduleFile1, file1Consumer1], session); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); - //}); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); - //it("should always return the file itself if '--isolatedModules' is specified", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "compileOnSave": true, - // "compilerOptions": { - // "isolatedModules": true - // } - // }` - // }; + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: FileOrFolder = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { + moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile + } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); - // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); - // openFilesForSession([moduleFile1], session); + const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path); + file1Consumer1.content += "export var T: number;"; + verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]); - // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: moduleFile1.path, - // line: 1, - // offset: 27, - // endLine: 1, - // endOffset: 27, - // insertString: `Point,` - // }); - // session.executeCommand(file1ChangeShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - //}); + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); - //it("should always return the file itself if '--out' or '--outFile' is specified", () => { - // configFile = { - // path: "/a/b/tsconfig.json", - // content: `{ - // "compileOnSave": true, - // "compilerOptions": { - // "module": "system", - // "outFile": "/a/b/out.js" - // } - // }` - // }; + // Change both files before the timeout + file1Consumer1.content += "export var T2: number;"; + moduleFile1.content = `export var T2: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]); + }); - // const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); - // openFilesForSession([moduleFile1], session); + it("should work fine for files with circular references", () => { + // TODO: do not exit on such errors? Just continue to watch the files for update in watch mode - // const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: moduleFile1.path, - // line: 1, - // offset: 27, - // endLine: 1, - // endOffset: 27, - // insertString: `Point,` - // }); - // session.executeCommand(file1ChangeShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]); - //}); + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: ` + /// + export var t1 = 10;` + }; + const file2: FileOrFolder = { + path: "/a/b/file2.ts", + content: ` + /// + export var t2 = 10;` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [file1.path, file2.path], + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath] + }); + const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path); - //it("should return cascaded affected file list", () => { - // const file1Consumer1Consumer1: FileOrFolder = { - // path: "/a/b/file1Consumer1Consumer1.ts", - // content: `import {y} from "./file1Consumer1";` - // }; - // const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + file1Emit.content += "export var t3 = 10;"; + verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]); - // openFilesForSession([moduleFile1, file1Consumer1], session); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); + }); - // const changeFile1Consumer1ShapeRequest = makeSessionRequest(CommandNames.Change, { - // file: file1Consumer1.path, - // line: 2, - // offset: 1, - // endLine: 2, - // endOffset: 1, - // insertString: `export var T: number;` - // }); - // session.executeCommand(changeModuleFile1ShapeRequest1); - // session.executeCommand(changeFile1Consumer1ShapeRequest); - // sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); - //}); + it("should detect removed code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath] + }); - //it("should work fine for files with circular references", () => { - // const file1: FileOrFolder = { - // path: "/a/b/file1.ts", - // content: ` - // /// - // export var t1 = 10;` - // }; - // const file2: FileOrFolder = { - // path: "/a/b/file2.ts", - // content: ` - // /// - // export var t2 = 10;` - // }; - // const host = createServerHost([file1, file2, configFile]); - // const typingsInstaller = createTestTypingsInstaller(host); - // const session = createSession(host, typingsInstaller); + const referenceFile1Emit = getFile(referenceFile1.path); + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + }); - // openFilesForSession([file1, file2], session); - // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]); - //}); + it("should detect non-existing code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { + configFile, + moduleFile2, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath] + }); - //it("should return results for all projects if not specifying projectFileName", () => { - // const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" }; - // const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` }; - // const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` }; - // const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` }; - // const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; + const referenceFile1Emit = getFile(referenceFile1.path); + referenceFile1Emit.content += "export var yy = Foo();"; + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); - // const host = createServerHost([file1, file2, file3, configFile1, configFile2]); - // const session = createSession(host); - - // openFilesForSession([file1, file2, file3], session); - // const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); - - // sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [ - // { projectFileName: configFile1.path, files: [file1, file2] }, - // { projectFileName: configFile2.path, files: [file1, file3] } - // ]); - //}); - - //it("should detect removed code file", () => { - // const referenceFile1: FileOrFolder = { - // path: "/a/b/referenceFile1.ts", - // content: ` - // /// - // export var x = Foo();` - // }; - // const host = createServerHost([moduleFile1, referenceFile1, configFile]); - // const session = createSession(host); - - // openFilesForSession([referenceFile1], session); - // host.reloadFS([referenceFile1, configFile]); - - // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, request, [ - // { projectFileName: configFile.path, files: [referenceFile1] } - // ]); - // const requestForMissingFile = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []); - //}); - - //it("should detect non-existing code file", () => { - // const referenceFile1: FileOrFolder = { - // path: "/a/b/referenceFile1.ts", - // content: ` - // /// - // export var x = Foo();` - // }; - // const host = createServerHost([referenceFile1, configFile]); - // const session = createSession(host); - - // openFilesForSession([referenceFile1], session); - // const request = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); - // sendAffectedFileRequestAndCheckResult(session, request, [ - // { projectFileName: configFile.path, files: [referenceFile1] } - // ]); - //}); + // Create module File2 and see both files are saved + verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]); + }); }); - } diff --git a/src/server/project.ts b/src/server/project.ts index 942ff9f71a4..02e2fb20759 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -130,7 +130,7 @@ namespace ts.server { /*@internal*/ lsHost: LSHost; - builder: Builder; + builder: Builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -250,7 +250,7 @@ namespace ts.server { if (!this.builder) { this.builder = createBuilder( this.projectService.toCanonicalFileName, - (_program, sourceFile, emitOnlyDts) => this.getFileEmitOutput(sourceFile, emitOnlyDts), + (_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed), data => this.projectService.host.createHash(data), sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent ); @@ -418,11 +418,11 @@ namespace ts.server { }); } - private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean) { + private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) { if (!this.languageServiceEnabled) { return undefined; } - return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles); + return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { diff --git a/src/services/services.ts b/src/services/services.ts index 8fe158a4798..40a446e0967 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1468,12 +1468,12 @@ namespace ts { return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput { + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers); + return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers); } // Signature help diff --git a/src/services/types.ts b/src/services/types.ts index 34d79b311ca..e17df08378a 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -275,7 +275,7 @@ namespace ts { getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; - getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed; getProgram(): Program; From bb91b32a4d0e3be9a4567034ad53ecf4461bff12 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Aug 2017 13:39:39 -0700 Subject: [PATCH 042/144] Add tests to verify emitted files --- src/compiler/tscLib.ts | 2 +- src/compiler/utilities.ts | 7 +- src/harness/unittests/tscWatchMode.ts | 119 ++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts index 13c1b27a5b0..36c9428cecd 100644 --- a/src/compiler/tscLib.ts +++ b/src/compiler/tscLib.ts @@ -292,7 +292,7 @@ namespace ts { } function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const newLine = getNewLineCharacter(options); + const newLine = getNewLineCharacter(options, system); const realpath = host.realpath && ((path: string) => host.realpath(path)); return { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0a38e6af58b..bea28ed2702 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3209,17 +3209,14 @@ namespace ts { const carriageReturnLineFeed = "\r\n"; const lineFeed = "\n"; - export function getNewLineCharacter(options: CompilerOptions | PrinterOptions): string { + export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: System): string { switch (options.newLine) { case NewLineKind.CarriageReturnLineFeed: return carriageReturnLineFeed; case NewLineKind.LineFeed: return lineFeed; } - if (sys) { - return sys.newLine; - } - return carriageReturnLineFeed; + return system ? system.newLine : sys ? sys.newLine : carriageReturnLineFeed; } /** diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 7622bc2556b..a55cfb133d7 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1345,4 +1345,123 @@ namespace ts.tscWatch { verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]); }); }); + + describe("tsc-watch emit file content", () => { + interface EmittedFile extends FileOrFolder { + shouldBeWritten: boolean; + } + function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] { + return map(contents, (content, index) => { + return { + content, + path: changeExtension(files[index].path, Extension.Js), + shouldBeWritten: true + }; + } + ); + } + function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) { + for (const { path, content, shouldBeWritten } of emittedFiles) { + if (shouldBeWritten) { + assert.isTrue(host.fileExists(path), `Expected file ${path} to be present`); + assert.equal(host.readFile(path), content, `Contents of file ${path} do not match`); + } + else { + assert.isNotTrue(host.fileExists(path), `Expected file ${path} to be absent`); + } + } + } + + function verifyEmittedFileContents(newLine: string, inputFiles: FileOrFolder[], initialEmittedFileContents: string[], + modifyFiles: (files: FileOrFolderEmit[], emitedFiles: EmittedFile[]) => FileOrFolderEmit[], configFile?: FileOrFolder) { + const host = createWatchedSystem([], { newLine }); + const files = concatenate( + map(inputFiles, file => getFileOrFolderEmit(file, fileToConvert => getEmittedLineForMultiFileOutput(fileToConvert, host))), + configFile ? [libFile, configFile] : [libFile] + ); + const allEmittedFiles = getEmittedLines(files); + host.reloadFS(files); + + // Initial compile + if (configFile) { + createWatchWithConfig(configFile.path, host); + } + else { + // First file as the root + createWatchModeWithoutConfigFile([files[0].path], { listEmittedFiles: true }, host); + } + checkOutputContains(host, allEmittedFiles); + + const emittedFiles = getEmittedFiles(files, initialEmittedFileContents); + verifyEmittedFiles(host, emittedFiles); + host.clearOutput(); + + const affectedFiles = modifyFiles(files, emittedFiles); + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(1); + checkAffectedLines(host, affectedFiles, allEmittedFiles); + + verifyEmittedFiles(host, emittedFiles); + } + + function verifyNewLine(newLine: string) { + const lines = ["var x = 1;", "var y = 2;"]; + const fileContent = lines.join(newLine); + const f = { + path: "/a/app.ts", + content: fileContent + }; + + verifyEmittedFileContents(newLine, [f], [fileContent + newLine], modifyFiles); + + function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { + files[0].content = fileContent + newLine + "var z = 3;"; + emittedFiles[0].content = files[0].content + newLine; + return [files[0]]; + } + } + + it("handles new lines: \\n", () => { + verifyNewLine("\n"); + }); + + it("handles new lines: \\r\\n", () => { + verifyNewLine("\r\n"); + }); + + it("should emit specified file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export function Foo() { return 10; }` + }; + + const file2 = { + path: "/a/b/f2.ts", + content: `import {Foo} from "./f1"; export let y = Foo();` + }; + + const file3 = { + path: "/a/b/f3.ts", + content: `import {y} from "./f2"; let x = y;` + }; + + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { listEmittedFiles: true } }) + }; + + verifyEmittedFileContents("\r\n", [file1, file2, file3], [ + `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`, + `"use strict";\r\nexports.__esModule = true;\r\nvar f1_1 = require("./f1");\r\nexports.y = f1_1.Foo();\r\n`, + `"use strict";\r\nexports.__esModule = true;\r\nvar f2_1 = require("./f2");\r\nvar x = f2_1.y;\r\n` + ], modifyFiles, configFile); + + function modifyFiles(files: FileOrFolderEmit[], emittedFiles: EmittedFile[]) { + files[0].content += `export function foo2() { return 2; }`; + emittedFiles[0].content += `function foo2() { return 2; }\r\nexports.foo2 = foo2;\r\n`; + emittedFiles[2].shouldBeWritten = false; + return files.slice(0, 2); + } + }); + }); } From 46e3d1c1d9054f55b0aa8ea5d1a062f6c4f3c2b2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Aug 2017 19:14:47 -0700 Subject: [PATCH 043/144] Refactoring so that instead of just using from tsc --watch the new api is accessible all the time --- src/compiler/program.ts | 24 +- src/compiler/tscLib.ts | 743 ++------------------------ src/compiler/tsconfig.json | 1 + src/compiler/watchedProgram.ts | 673 +++++++++++++++++++++++ src/harness/unittests/tscWatchMode.ts | 101 ++-- 5 files changed, 800 insertions(+), 742 deletions(-) create mode 100644 src/compiler/watchedProgram.ts diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 80bd089429a..8ce51ac5e8c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -228,19 +228,25 @@ namespace ts { let output = ""; for (const diagnostic of diagnostics) { - if (diagnostic.file) { - const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); - const fileName = diagnostic.file.fileName; - const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); - output += `${relativeFileName}(${line + 1},${character + 1}): `; - } - - const category = DiagnosticCategory[diagnostic.category].toLowerCase(); - output += `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; + output += formatDiagnostic(diagnostic, host); } return output; } + export function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string { + const category = DiagnosticCategory[diagnostic.category].toLowerCase(); + const errorMessage = `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, host.getNewLine())}${host.getNewLine()}`; + + if (diagnostic.file) { + const { line, character } = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); + const fileName = diagnostic.file.fileName; + const relativeFileName = convertToRelativePath(fileName, host.getCurrentDirectory(), fileName => host.getCanonicalFileName(fileName)); + return `${relativeFileName}(${line + 1},${character + 1}): ` + errorMessage; + } + + return errorMessage; + } + const redForegroundEscapeSequence = "\u001b[91m"; const yellowForegroundEscapeSequence = "\u001b[93m"; const blueForegroundEscapeSequence = "\u001b[93m"; diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts index 36c9428cecd..bd4a64d8899 100644 --- a/src/compiler/tscLib.ts +++ b/src/compiler/tscLib.ts @@ -1,63 +1,13 @@ /// +/// /// namespace ts { - export interface CompilerHost { - /** If this is the emit based on the graph builder, use it to emit */ - emitWithBuilder?(program: Program): EmitResult; - } - interface Statistic { name: string; value: string; } - export interface FormatDiagnosticsHostWithWrite extends FormatDiagnosticsHost { - write?(s: string): void; - } - - const defaultFormatDiagnosticsHost: FormatDiagnosticsHostWithWrite = sys ? { - getCurrentDirectory: () => sys.getCurrentDirectory(), - getNewLine: () => sys.newLine, - getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames), - write: s => sys.write(s) - } : undefined; - - function getDefaultFormatDiagnosticsHost(system: System): FormatDiagnosticsHostWithWrite { - return system === sys ? defaultFormatDiagnosticsHost : { - getCurrentDirectory: () => system.getCurrentDirectory(), - getNewLine: () => system.newLine, - getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - write: s => system.write(s) - }; - } - - let reportDiagnosticWorker = reportDiagnosticSimply; - - function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite) { - reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost); - } - - function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHostWithWrite): void { - for (const diagnostic of diagnostics) { - reportDiagnostic(diagnostic, host); - } - } - - function reportEmittedFiles(files: string[], system: System): void { - if (!files || files.length === 0) { - return; - } - - const currentDir = system.getCurrentDirectory(); - - for (const file of files) { - const filepath = getNormalizedAbsolutePath(file, currentDir); - - system.write(`TSFILE: ${filepath}${system.newLine}`); - } - } - function countLines(program: Program): number { let count = 0; forEach(program.getSourceFiles(), file => { @@ -71,25 +21,11 @@ namespace ts { return diagnostic.messageText; } - function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void { - host.write(ts.formatDiagnostics([diagnostic], host)); - } - - function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHostWithWrite): void { - host.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); - } - - function reportWatchDiagnostic(diagnostic: Diagnostic, system: System) { - let output = new Date().toLocaleTimeString() + " - "; - - if (diagnostic.file) { - const loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); - output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `; + let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply); + function udpateReportDiagnostic(options: CompilerOptions) { + if (options.pretty) { + reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext); } - - output += `${ flattenDiagnosticMessageText(diagnostic.messageText, system.newLine) }${ system.newLine + system.newLine + system.newLine }`; - - system.write(output); } function padLeft(s: string, length: number) { @@ -118,7 +54,7 @@ namespace ts { let configFileName: string; if (commandLine.options.locale) { if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale")); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors); @@ -127,7 +63,7 @@ namespace ts { // If there are any errors due to command line parsing and/or // setting up localization, report them and quit. if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, /*host*/ undefined); + reportDiagnostics(commandLine.errors, reportDiagnostic); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -149,11 +85,11 @@ namespace ts { if (commandLine.options.project) { if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project")); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } if (commandLine.fileNames.length !== 0) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line)); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -161,14 +97,14 @@ namespace ts { if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) { configFileName = combinePaths(fileOrDirectory, "tsconfig.json"); if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project)); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } } else { configFileName = fileOrDirectory; if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project)); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } } @@ -186,599 +122,70 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys); + const reportWatchDiagnostic = createWatchDiagnosticReporter(); + const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic); + udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithConfigFile(configParseResult, commandLineOptions, sys); + createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic)); } else { performCompilation(configParseResult.fileNames, configParseResult.options); } } else { - if (isWatchSet(commandLine.options)) { + udpateReportDiagnostic(commandLineOptions); + if (isWatchSet(commandLineOptions)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, sys); + createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost()); } else { performCompilation(commandLine.fileNames, commandLineOptions); } } + } - function reportWatchModeWithoutSysSupport() { - if (!sys.watchFile || !sys.watchDirectory) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - - function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { - if (compilerOptions.pretty) { - reportDiagnosticWorker = reportDiagnosticWithColorAndContext; - } - - const compilerHost = createCompilerHost(compilerOptions); - const compileResult = compile(rootFileNames, compilerOptions, compilerHost, sys); - return sys.exit(compileResult.exitStatus); + function reportWatchModeWithoutSysSupport() { + if (!sys.watchFile || !sys.watchDirectory) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch")); + sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } } - interface HostFileInfo { - version: number; - sourceFile: SourceFile; - fileWatcher: FileWatcher; + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { + const compilerHost = createCompilerHost(compilerOptions); + enableStatistics(compilerOptions); + + const program = createProgram(rootFileNames, compilerOptions, compilerHost); + const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); + + reportStatistics(program); + return sys.exit(exitStatus); } - /* @internal */ - export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions, system: System) { - return createWatchMode(configParseResult.fileNames, configParseResult.options, system, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); - } - - /* @internal */ - export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, system: System) { - return createWatchMode(rootFileNames, compilerOptions, system); - } - - function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, system: System, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { - let program: Program; - let needsReload: boolean; // true if the config file changed and needs to reload it from the disk - let missingFilesMap: Map; // Map of file watchers for the missing files - let configFileWatcher: FileWatcher; // watcher for the config file - let watchedWildCardDirectories: Map; // map of watchers for the wild card directories in the config file - let timerToUpdateProgram: any; // timer callback to recompile the program - - const sourceFilesCache = createMap(); // Cache that stores the source file and version info - - let host: System; - if (configFileName) { - host = createCachedSystem(system); - configFileWatcher = system.watchFile(configFileName, onConfigFileChanged); - } - else { - host = system; - } - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - - // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); - - if (compilerOptions.pretty) { - reportDiagnosticWorker = reportDiagnosticWithColorAndContext; - } - - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - - return () => program; - - function synchronizeProgram() { - writeLog(`Synchronizing program`); - - if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) { - return; - } - - // Create the compiler host - const compilerHost = createWatchedCompilerHost(compilerOptions); - program = compile(rootFileNames, compilerOptions, compilerHost, system, program).program; - - // Update watches - missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); - - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes), system); - } - - function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const newLine = getNewLineCharacter(options, system); - const realpath = host.realpath && ((path: string) => host.realpath(path)); - - return { - getSourceFile: getVersionedSourceFile, - getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, - getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists, - readFile: fileName => host.readFile(fileName), - trace: (s: string) => host.write(s + newLine), - directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => host.getDirectories(path), - realpath, - onReleaseOldSourceFile, - emitWithBuilder - }; - - // TODO: cache module resolution - // if (host.resolveModuleNames) { - // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); - // } - // if (host.resolveTypeReferenceDirectives) { - // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { - // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); - // }; - // } - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - host.writeFile(fileName, data, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); - } - } - - function emitWithBuilder(program: Program): EmitResult { - builder.onProgramUpdateGraph(program); - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - - const result = builder.emitChangedFiles(program); - switch (result.length) { - case 0: - emitSkipped = true; - break; - case 1: - const emitOutput = result[0]; - ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); - writeOutputFiles(emitOutput.outputFiles); - break; - default: - for (const emitOutput of result) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - diagnostics = concatenate(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - writeOutputFiles(emitOutput.outputFiles); - } - } - - return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - - function writeOutputFiles(outputFiles: OutputFile[]) { - if (outputFiles) { - for (const outputFile of outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - diagnostics.push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); - } - } - } - } - } - } - - function fileExists(fileName: string) { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const hostSourceFileInfo = sourceFilesCache.get(path); - if (hostSourceFileInfo !== undefined) { - return !isString(hostSourceFileInfo); - } - - return host.fileExists(fileName); - } - - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(host.getExecutingFilePath())); - } - - function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); - } - - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - const hostSourceFile = sourceFilesCache.get(path); - // No source file on the host - if (isString(hostSourceFile)) { - return undefined; - } - - // Create new source file if requested or the versions dont match - if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { - const sourceFile = getNewSourceFile(); - if (hostSourceFile) { - if (shouldCreateNewSourceFile) { - hostSourceFile.version++; - } - if (sourceFile) { - hostSourceFile.sourceFile = sourceFile; - sourceFile.version = hostSourceFile.version.toString(); - if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchSourceFileForChanges(path); - } - } - else { - // There is no source file on host any more, close the watch, missing file paths will track it - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, hostSourceFile.version.toString()); - } - } - else { - let fileWatcher: FileWatcher; - if (sourceFile) { - sourceFile.version = "0"; - fileWatcher = watchSourceFileForChanges(path); - sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); - } - else { - sourceFilesCache.set(path, "0"); - } - } - return sourceFile; - } - return hostSourceFile.sourceFile; - - function getNewSourceFile() { - let text: string; - try { - performance.mark("beforeIORead"); - text = host.readFile(fileName, compilerOptions.charset); - performance.mark("afterIORead"); - performance.measure("I/O Read", "beforeIORead", "afterIORead"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - - return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; - } - } - - function removeSourceFile(path: Path) { - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile !== undefined) { - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - } - sourceFilesCache.delete(path); - } - } - - function getSourceVersion(path: Path): string { - const hostSourceFile = sourceFilesCache.get(path); - return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); - } - - function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { - const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); - // If this is the source file thats in the cache and new program doesnt need it, - // remove the cached entry. - // Note we arent deleting entry if file became missing in new program or - // there was version update and new source file was created. - if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { - sourceFilesCache.delete(oldSourceFile.path); - } - } - - // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch - // operations (such as saving all modified files in an editor) a chance to complete before we kick - // off a new compilation. - function scheduleProgramUpdate() { - if (!system.setTimeout || !system.clearTimeout) { - return; - } - - if (timerToUpdateProgram) { - system.clearTimeout(timerToUpdateProgram); - } - timerToUpdateProgram = system.setTimeout(updateProgram, 250); - } - - function scheduleProgramReload() { - Debug.assert(!!configFileName); - needsReload = true; - scheduleProgramUpdate(); - } - - function updateProgram() { - timerToUpdateProgram = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation), system); - - if (needsReload) { - reloadConfigFile(); - } - else { - synchronizeProgram(); - } - } - - function reloadConfigFile() { - writeLog(`Reloading config file: ${configFileName}`); - needsReload = false; - - const cachedHost = host as CachedSystem; - cachedHost.clearCache(); - const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost); - rootFileNames = configParseResult.fileNames; - compilerOptions = configParseResult.options; - configFileSpecs = configParseResult.configFileSpecs; - configFileWildCardDirectories = configParseResult.wildcardDirectories; - - synchronizeProgram(); - - // Update the wild card directory watch - watchConfigFileWildCardDirectories(); - } - - function watchSourceFileForChanges(path: Path) { - return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); - } - - function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - - updateCachedSystem(fileName, path); - const hostSourceFile = sourceFilesCache.get(path); - if (hostSourceFile) { - // Update the cache - if (eventKind === FileWatcherEventKind.Deleted) { - if (!isString(hostSourceFile)) { - hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, (hostSourceFile.version++).toString()); - } - } - else { - // Deleted file created - if (isString(hostSourceFile)) { - sourceFilesCache.delete(path); - } - else { - // file changed - just update the version - hostSourceFile.version++; - } - } - } - - // Update the program - scheduleProgramUpdate(); - } - - function updateCachedSystem(fileName: string, path: Path) { - if (configFileName) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(fileName, getDirectoryPath(path)); - (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); - } - } - - function watchMissingFilePath(missingFilePath: Path) { - return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); - } - - function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { - fileWatcher.close(); - } - - function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { - writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); - if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { - closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); - missingFilesMap.delete(missingFilePath); - - updateCachedSystem(filename, missingFilePath); - - // Delete the entry in the source files cache so that new source file is created - removeSourceFile(missingFilePath); - - // When a missing file is created, we should update the graph. - scheduleProgramUpdate(); - } - } - - function watchConfigFileWildCardDirectories() { - const wildcards = createMapFromTemplate(configFileWildCardDirectories); - watchedWildCardDirectories = updateWatchingWildcardDirectories( - watchedWildCardDirectories, wildcards, - watchWildCardDirectory, stopWatchingWildCardDirectory - ); - } - - function watchWildCardDirectory(directory: string, recursive: boolean) { - return host.watchDirectory(directory, fileName => - onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), - recursive); - } - - function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) { - fileWatcher.close(); - } - - function onFileAddOrRemoveInWatchedDirectory(fileName: string) { - Debug.assert(!!configFileName); - - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - - // Since the file existance changed, update the sourceFiles cache - updateCachedSystem(fileName, path); - removeSourceFile(path); - - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); - return; - } - - writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); - - // Reload is pending, do the reload - if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtensions*/ []); - if (!configFileSpecs.filesSpecs) { - reportDiagnostics([getErrorForNoInputFiles(configFileSpecs, configFileName)], getDefaultFormatDiagnosticsHost(system)); - } - rootFileNames = result.fileNames; - - // Schedule Update the program - scheduleProgramUpdate(); - } - } - - function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { - writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - scheduleProgramReload(); - } - - function writeLog(s: string) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - if (hasDiagnostics) { - host.write(s); - } - } - - function computeHash(data: string) { - return system.createHash ? system.createHash(data) : data; - } - } - - interface CachedSystem extends System { - addOrDeleteFileOrFolder(fileOrFolder: string): void; - clearCache(): void; - } - - function createCachedSystem(host: System): CachedSystem { - const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; - const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; - const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; - const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; - const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; - const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; - const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; - const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; - const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; - const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; - - const cachedHost = createCachedHost(host); - return { - args: host.args, - newLine: host.newLine, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - write: s => host.write(s), - readFile: (path, encoding?) => host.readFile(path, encoding), - getFileSize, - writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), - watchFile, - watchDirectory, - resolvePath: path => host.resolvePath(path), - fileExists: fileName => cachedHost.fileExists(fileName), - directoryExists: dir => cachedHost.directoryExists(dir), - createDirectory: dir => cachedHost.createDirectory(dir), - getExecutingFilePath: () => host.getExecutingFilePath(), - getCurrentDirectory: () => cachedHost.getCurrentDirectory(), - getDirectories: dir => cachedHost.getDirectories(dir), - readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), - getModifiedTime, - createHash, - getMemoryUsage, - exit: exitCode => host.exit(exitCode), - realpath, - getEnvironmentVariable: name => host.getEnvironmentVariable(name), - tryEnableSourceMapsForHost, - debugMode: host.debugMode, - setTimeout, - clearTimeout, - addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), - clearCache: () => cachedHost.clearCache() + function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) { + const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); + watchingHost.beforeCompile = enableStatistics; + const afterCompile = watchingHost.afterCompile; + watchingHost.afterCompile = (host, program, builder) => { + afterCompile(host, program, builder); + reportStatistics(program); }; + return watchingHost; } - /* @internal */ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System): ParsedCommandLine { - let configFileText: string; - try { - configFileText = system.readFile(configFileName); - } - catch (e) { - const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error, system); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - if (!configFileText) { - const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], getDefaultFormatDiagnosticsHost(system)); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } - - const result = parseJsonText(configFileName, configFileText); - reportDiagnostics(result.parseDiagnostics, getDefaultFormatDiagnosticsHost(system)); - - const cwd = system.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, getDefaultFormatDiagnosticsHost(system)); - - return configParseResult; - } - - function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost, system: System, oldProgram?: Program) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - let statistics: Statistic[]; - if (hasDiagnostics) { + function enableStatistics(compilerOptions: CompilerOptions) { + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); + } + } + + function reportStatistics(program: Program) { + let statistics: Statistic[]; + const compilerOptions = program.getCompilerOptions(); + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { statistics = []; - } - - const program = createProgram(fileNames, compilerOptions, compilerHost, oldProgram); - const exitStatus = compileProgram(); - - if (compilerOptions.listFiles) { - forEach(program.getSourceFiles(), file => { - system.write(file.fileName + system.newLine); - }); - } - - if (hasDiagnostics) { - const memoryUsed = system.getMemoryUsage ? system.getMemoryUsage() : -1; + const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); reportCountStatistic("Nodes", program.getNodeCount()); @@ -815,50 +222,6 @@ namespace ts { performance.disable(); } - return { program, exitStatus }; - - function compileProgram(): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); - } - } - - // Emit and report any errors we ran into. - let emitOutput: EmitResult; - if (compilerHost.emitWithBuilder) { - emitOutput = compilerHost.emitWithBuilder(program); - } - else { - // Emit whole program - emitOutput = program.emit(); - } - diagnostics = diagnostics.concat(emitOutput.diagnostics); - - reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), getDefaultFormatDiagnosticsHost(system)); - - reportEmittedFiles(emitOutput.emittedFiles, system); - if (emitOutput.emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; - } - function reportStatistics() { let nameSize = 0; let valueSize = 0; @@ -873,7 +236,7 @@ namespace ts { } for (const { name, value } of statistics) { - system.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + system.newLine); + sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); } } @@ -1012,11 +375,11 @@ namespace ts { const currentDirectory = sys.getCurrentDirectory(); const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); if (sys.fileExists(file)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file)); } else { sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* host */ undefined); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file)); } return; diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 360819f2b36..fd92a6008cd 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -37,6 +37,7 @@ "emitter.ts", "program.ts", "builder.ts", + "watchedProgram.ts", "commandLineParser.ts", "tscLib.ts", "tsc.ts", diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts new file mode 100644 index 00000000000..22625282a8d --- /dev/null +++ b/src/compiler/watchedProgram.ts @@ -0,0 +1,673 @@ +/// +/// + +namespace ts { + export type DiagnosticReporter = (diagnostic: Diagnostic) => void; + export type DiagnosticWorker = (diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System) => void; + export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; + export interface WatchingSystemHost { + // FS system to use + system: System; + + // parse config file + parseConfigFile: ParseConfigFile; + + // Reporting errors + reportDiagnostic: DiagnosticReporter; + reportWatchDiagnostic: DiagnosticReporter; + + // Callbacks to do custom action before creating program and after creating program + beforeCompile(compilerOptions: CompilerOptions): void; + afterCompile(host: System, program: Program, builder: Builder): void; + } + + const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { + getCurrentDirectory: () => sys.getCurrentDirectory(), + getNewLine: () => sys.newLine, + getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) + } : undefined; + + export function createDiagnosticReporter(system = sys, worker = reportDiagnosticSimply, formatDiagnosticsHost?: FormatDiagnosticsHost): DiagnosticReporter { + return diagnostic => worker(diagnostic, getFormatDiagnosticsHost(), system); + + function getFormatDiagnosticsHost() { + return formatDiagnosticsHost || (formatDiagnosticsHost = system === sys ? defaultFormatDiagnosticsHost : { + getCurrentDirectory: () => system.getCurrentDirectory(), + getNewLine: () => system.newLine, + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + }); + } + } + + export function createWatchDiagnosticReporter(system = sys): DiagnosticReporter { + return diagnostic => { + let output = new Date().toLocaleTimeString() + " - "; + output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine + system.newLine}`; + system.write(output); + }; + } + + export function reportDiagnostics(diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter): void { + for (const diagnostic of diagnostics) { + reportDiagnostic(diagnostic); + } + } + + export function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { + system.write(ts.formatDiagnostic(diagnostic, host)); + } + + export function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { + system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); + } + + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + let configFileText: string; + try { + configFileText = system.readFile(configFileName); + } + catch (e) { + const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); + reportWatchDiagnostic(error); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + if (!configFileText) { + const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); + reportDiagnostics([error], reportDiagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + return; + } + + const result = parseJsonText(configFileName, configFileText); + reportDiagnostics(result.parseDiagnostics, reportDiagnostic); + + const cwd = system.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + reportDiagnostics(configParseResult.errors, reportDiagnostic); + + return configParseResult; + } + + function reportEmittedFiles(files: string[], system: System): void { + if (!files || files.length === 0) { + return; + } + const currentDir = system.getCurrentDirectory(); + for (const file of files) { + const filepath = getNormalizedAbsolutePath(file, currentDir); + system.write(`TSFILE: ${filepath}${system.newLine}`); + } + } + + export function compileProgram(system: System, program: Program, emitProgram: () => EmitResult, + reportDiagnostic: DiagnosticReporter): ExitStatus { + let diagnostics: Diagnostic[]; + + // First get and report any syntactic errors. + diagnostics = program.getSyntacticDiagnostics(); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + diagnostics = program.getSemanticDiagnostics(); + } + } + + // Emit and report any errors we ran into. + const emitOutput = emitProgram(); + diagnostics = diagnostics.concat(emitOutput.diagnostics); + + reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic); + reportEmittedFiles(emitOutput.emittedFiles, system); + + if (program.getCompilerOptions().listFiles) { + forEach(program.getSourceFiles(), file => { + system.write(file.fileName + system.newLine); + }); + } + + if (emitOutput.emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; + } + + function emitWatchedProgram(host: System, program: Program, builder: Builder) { + const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; + let sourceMaps: SourceMapData[]; + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + + const result = builder.emitChangedFiles(program); + switch (result.length) { + case 0: + emitSkipped = true; + break; + case 1: + const emitOutput = result[0]; + ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); + writeOutputFiles(emitOutput.outputFiles); + break; + default: + for (const emitOutput of result) { + if (emitOutput.emitSkipped) { + emitSkipped = true; + } + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + writeOutputFiles(emitOutput.outputFiles); + } + } + + return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; + + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, data, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); + } + } + + function writeOutputFiles(outputFiles: OutputFile[]) { + if (outputFiles) { + for (const outputFile of outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + diagnostics.push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } + } + } + } + } + + export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys, + parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter, + reportWatchDiagnostic?: DiagnosticReporter + ): WatchingSystemHost { + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); + reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); + parseConfigFile = parseConfigFile || ts.parseConfigFile; + return { + system, + parseConfigFile, + reportDiagnostic, + reportWatchDiagnostic, + beforeCompile: noop, + afterCompile: compileWatchedProgram, + }; + + function compileWatchedProgram(host: System, program: Program, builder: Builder) { + return compileProgram(system, program, () => emitWatchedProgram(host, program, builder), reportDiagnostic); + } + } + + export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions = {}, watchingHost?: WatchingSystemHost) { + return createWatchMode(configParseResult.fileNames, configParseResult.options, watchingHost, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); + } + + export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost) { + return createWatchMode(rootFileNames, compilerOptions, watchingHost); + } + + interface HostFileInfo { + version: number; + sourceFile: SourceFile; + fileWatcher: FileWatcher; + } + + function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { + let program: Program; + let needsReload: boolean; // true if the config file changed and needs to reload it from the disk + let missingFilesMap: Map; // Map of file watchers for the missing files + let configFileWatcher: FileWatcher; // watcher for the config file + let watchedWildCardDirectories: Map; // map of watchers for the wild card directories in the config file + let timerToUpdateProgram: any; // timer callback to recompile the program + + const sourceFilesCache = createMap(); // Cache that stores the source file and version info + watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); + const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; + + let host: System; + if (configFileName) { + host = createCachedSystem(system); + configFileWatcher = system.watchFile(configFileName, onConfigFileChanged); + } + else { + host = system; + } + const currentDirectory = host.getCurrentDirectory(); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + + // There is no extra check needed since we can just rely on the program to decide emit + const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + + return () => program; + + function synchronizeProgram() { + writeLog(`Synchronizing program`); + + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) { + return; + } + + // Create the compiler host + const compilerHost = createWatchedCompilerHost(compilerOptions); + beforeCompile(compilerOptions); + + // Compile the program + program = createProgram(rootFileNames, compilerOptions, compilerHost, program); + builder.onProgramUpdateGraph(program); + + // Update watches + missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); + + afterCompile(host, program, builder); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + } + + function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { + const newLine = getNewLineCharacter(options, system); + const realpath = host.realpath && ((path: string) => host.realpath(path)); + + return { + getSourceFile: getVersionedSourceFile, + getSourceFileByPath: getVersionedSourceFileByPath, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + getCurrentDirectory: memoize(() => host.getCurrentDirectory()), + useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists, + readFile: fileName => host.readFile(fileName), + trace: (s: string) => host.write(s + newLine), + directoryExists: directoryName => host.directoryExists(directoryName), + getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", + getDirectories: (path: string) => host.getDirectories(path), + realpath, + onReleaseOldSourceFile, + }; + + // TODO: cache module resolution + // if (host.resolveModuleNames) { + // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + // } + // if (host.resolveTypeReferenceDirectives) { + // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { + // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); + // }; + // } + } + + function fileExists(fileName: string) { + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const hostSourceFileInfo = sourceFilesCache.get(path); + if (hostSourceFileInfo !== undefined) { + return !isString(hostSourceFileInfo); + } + + return host.fileExists(fileName); + } + + function getDefaultLibLocation(): string { + return getDirectoryPath(normalizePath(host.getExecutingFilePath())); + } + + function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + } + + function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { + const hostSourceFile = sourceFilesCache.get(path); + // No source file on the host + if (isString(hostSourceFile)) { + return undefined; + } + + // Create new source file if requested or the versions dont match + if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) { + const sourceFile = getNewSourceFile(); + if (hostSourceFile) { + if (shouldCreateNewSourceFile) { + hostSourceFile.version++; + } + if (sourceFile) { + hostSourceFile.sourceFile = sourceFile; + sourceFile.version = hostSourceFile.version.toString(); + if (!hostSourceFile.fileWatcher) { + hostSourceFile.fileWatcher = watchSourceFileForChanges(path); + } + } + else { + // There is no source file on host any more, close the watch, missing file paths will track it + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, hostSourceFile.version.toString()); + } + } + else { + let fileWatcher: FileWatcher; + if (sourceFile) { + sourceFile.version = "0"; + fileWatcher = watchSourceFileForChanges(path); + sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + } + else { + sourceFilesCache.set(path, "0"); + } + } + return sourceFile; + } + return hostSourceFile.sourceFile; + + function getNewSourceFile() { + let text: string; + try { + performance.mark("beforeIORead"); + text = host.readFile(fileName, compilerOptions.charset); + performance.mark("afterIORead"); + performance.measure("I/O Read", "beforeIORead", "afterIORead"); + } + catch (e) { + if (onError) { + onError(e.message); + } + } + + return text !== undefined ? createSourceFile(fileName, text, languageVersion) : undefined; + } + } + + function removeSourceFile(path: Path) { + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile !== undefined) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + } + sourceFilesCache.delete(path); + } + } + + function getSourceVersion(path: Path): string { + const hostSourceFile = sourceFilesCache.get(path); + return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); + } + + function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions) { + const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.path); + // If this is the source file thats in the cache and new program doesnt need it, + // remove the cached entry. + // Note we arent deleting entry if file became missing in new program or + // there was version update and new source file was created. + if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { + sourceFilesCache.delete(oldSourceFile.path); + } + } + + // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch + // operations (such as saving all modified files in an editor) a chance to complete before we kick + // off a new compilation. + function scheduleProgramUpdate() { + if (!system.setTimeout || !system.clearTimeout) { + return; + } + + if (timerToUpdateProgram) { + system.clearTimeout(timerToUpdateProgram); + } + timerToUpdateProgram = system.setTimeout(updateProgram, 250); + } + + function scheduleProgramReload() { + Debug.assert(!!configFileName); + needsReload = true; + scheduleProgramUpdate(); + } + + function updateProgram() { + timerToUpdateProgram = undefined; + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + + if (needsReload) { + reloadConfigFile(); + } + else { + synchronizeProgram(); + } + } + + function reloadConfigFile() { + writeLog(`Reloading config file: ${configFileName}`); + needsReload = false; + + const cachedHost = host as CachedSystem; + cachedHost.clearCache(); + const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); + rootFileNames = configParseResult.fileNames; + compilerOptions = configParseResult.options; + configFileSpecs = configParseResult.configFileSpecs; + configFileWildCardDirectories = configParseResult.wildcardDirectories; + + synchronizeProgram(); + + // Update the wild card directory watch + watchConfigFileWildCardDirectories(); + } + + function watchSourceFileForChanges(path: Path) { + return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); + } + + function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { + writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + + updateCachedSystem(fileName, path); + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile) { + // Update the cache + if (eventKind === FileWatcherEventKind.Deleted) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); + sourceFilesCache.set(path, (hostSourceFile.version++).toString()); + } + } + else { + // Deleted file created + if (isString(hostSourceFile)) { + sourceFilesCache.delete(path); + } + else { + // file changed - just update the version + hostSourceFile.version++; + } + } + } + + // Update the program + scheduleProgramUpdate(); + } + + function updateCachedSystem(fileName: string, path: Path) { + if (configFileName) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(fileName, getDirectoryPath(path)); + (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); + } + } + + function watchMissingFilePath(missingFilePath: Path) { + return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); + } + + function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { + fileWatcher.close(); + } + + function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { + writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); + if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { + closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); + missingFilesMap.delete(missingFilePath); + + updateCachedSystem(filename, missingFilePath); + + // Delete the entry in the source files cache so that new source file is created + removeSourceFile(missingFilePath); + + // When a missing file is created, we should update the graph. + scheduleProgramUpdate(); + } + } + + function watchConfigFileWildCardDirectories() { + const wildcards = createMapFromTemplate(configFileWildCardDirectories); + watchedWildCardDirectories = updateWatchingWildcardDirectories( + watchedWildCardDirectories, wildcards, + watchWildCardDirectory, stopWatchingWildCardDirectory + ); + } + + function watchWildCardDirectory(directory: string, recursive: boolean) { + return host.watchDirectory(directory, fileName => + onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), + recursive); + } + + function stopWatchingWildCardDirectory(_directory: string, fileWatcher: FileWatcher, _recursive: boolean, _recursiveChanged: boolean) { + fileWatcher.close(); + } + + function onFileAddOrRemoveInWatchedDirectory(fileName: string) { + Debug.assert(!!configFileName); + + const path = toPath(fileName, currentDirectory, getCanonicalFileName); + + // Since the file existance changed, update the sourceFiles cache + updateCachedSystem(fileName, path); + removeSourceFile(path); + + // If a change was made inside "folder/file", node will trigger the callback twice: + // one with the fileName being "folder/file", and the other one with "folder". + // We don't respond to the second one. + if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); + return; + } + + writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); + + // Reload is pending, do the reload + if (!needsReload) { + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtension*/ []); + if (!configFileSpecs.filesSpecs) { + reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + } + rootFileNames = result.fileNames; + + // Schedule Update the program + scheduleProgramUpdate(); + } + } + + function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { + writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + scheduleProgramReload(); + } + + function writeLog(s: string) { + const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + if (hasDiagnostics) { + host.write(s); + } + } + + function computeHash(data: string) { + return system.createHash ? system.createHash(data) : data; + } + } + + interface CachedSystem extends System { + addOrDeleteFileOrFolder(fileOrFolder: string): void; + clearCache(): void; + } + + function createCachedSystem(host: System): CachedSystem { + const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; + const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; + const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; + const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; + const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; + const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; + const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; + const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; + const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; + const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; + + const cachedHost = createCachedHost(host); + return { + args: host.args, + newLine: host.newLine, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, + write: s => host.write(s), + readFile: (path, encoding?) => host.readFile(path, encoding), + getFileSize, + writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), + watchFile, + watchDirectory, + resolvePath: path => host.resolvePath(path), + fileExists: fileName => cachedHost.fileExists(fileName), + directoryExists: dir => cachedHost.directoryExists(dir), + createDirectory: dir => cachedHost.createDirectory(dir), + getExecutingFilePath: () => host.getExecutingFilePath(), + getCurrentDirectory: () => cachedHost.getCurrentDirectory(), + getDirectories: dir => cachedHost.getDirectories(dir), + readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), + getModifiedTime, + createHash, + getMemoryUsage, + exit: exitCode => host.exit(exitCode), + realpath, + getEnvironmentVariable: name => host.getEnvironmentVariable(name), + tryEnableSourceMapsForHost, + debugMode: host.debugMode, + setTimeout, + clearTimeout, + addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), + clearCache: () => cachedHost.clearCache() + }; + } +} diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index a55cfb133d7..608fc30c6b9 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -26,9 +26,23 @@ namespace ts.tscWatch { checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); } - export function createWatchWithConfig(configFilePath: string, host: WatchedSystem) { - const configFileResult = parseConfigFile(configFilePath, {}, host); - return createWatchModeWithConfigFile(configFileResult, {}, host); + function createWatchingSystemHost(system: WatchedSystem) { + return ts.createWatchingSystemHost(/*pretty*/ undefined, system); + } + + function parseConfigFile(configFileName: string, watchingSystemHost: WatchingSystemHost) { + return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); + } + + function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) { + const watchingSystemHost = createWatchingSystemHost(host); + const configFileResult = parseConfigFile(configFilePath, watchingSystemHost); + return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); + } + + function createWatchModeWithoutConfigFile(fileNames: string[], host: WatchedSystem, options: CompilerOptions = {}) { + const watchingSystemHost = createWatchingSystemHost(host); + return ts.createWatchModeWithoutConfigFile(fileNames, options, watchingSystemHost); } function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { @@ -94,7 +108,7 @@ namespace ts.tscWatch { content: `export let x: number` }; const host = createWatchedSystem([appFile, moduleFile, libFile]); - const watch = createWatchModeWithoutConfigFile([appFile.path], {}, host); + const watch = createWatchModeWithoutConfigFile([appFile.path], host); checkProgramActualFiles(watch(), [appFile.path, libFile.path, moduleFile.path]); @@ -119,7 +133,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - const watch = createWatchWithConfig(upperCaseConfigFilePath, host); + const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host); checkProgramActualFiles(watch(), [f1.path]); }); @@ -148,10 +162,11 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); - const configFileResult = parseConfigFile(configFile.path, {}, host); + const watchingSystemHost = createWatchingSystemHost(host); + const configFileResult = parseConfigFile(configFile.path, watchingSystemHost); assert.equal(configFileResult.errors.length, 0, `expect no errors in config file, got ${JSON.stringify(configFileResult.errors)}`); - const watch = createWatchModeWithConfigFile(configFileResult, {}, host); + const watch = ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]); checkProgramRootFiles(watch(), [file1.path, file2.path]); @@ -169,7 +184,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, libFile, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); checkProgramRootFiles(watch(), [commonFile1.path]); @@ -192,7 +207,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); const commonFile3 = "/a/b/commonFile3.ts"; checkProgramRootFiles(watch(), [commonFile1.path, commonFile3]); @@ -205,7 +220,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); // delete commonFile2 @@ -226,7 +241,7 @@ namespace ts.tscWatch { let x = y` }; const host = createWatchedSystem([file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + const watch = createWatchModeWithoutConfigFile([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, libFile.path]); @@ -254,7 +269,7 @@ namespace ts.tscWatch { }; const files = [commonFile1, commonFile2, configFile]; const host = createWatchedSystem(files); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); configFile.content = `{ @@ -281,7 +296,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([commonFile1, commonFile2, excludedFile1, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -309,7 +324,7 @@ namespace ts.tscWatch { }; const files = [file1, nodeModuleFile, classicModuleFile, configFile]; const host = createWatchedSystem(files); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, nodeModuleFile.path]); @@ -337,7 +352,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -355,7 +370,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + const watch = createWatchModeWithoutConfigFile([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, file2.path]); @@ -384,7 +399,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], {}, host); + const watch = createWatchModeWithoutConfigFile([file1.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -407,7 +422,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], {}, host); + const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -435,7 +450,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, file3, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file2.path, file3.path]); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); @@ -457,10 +472,10 @@ namespace ts.tscWatch { content: "export let y = 1;" }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], {}, host); + const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], host); checkProgramActualFiles(watch(), [file2.path, file3.path]); - const watch2 = createWatchModeWithoutConfigFile([file1.path], {}, host); + const watch2 = createWatchModeWithoutConfigFile([file1.path], host); checkProgramActualFiles(watch2(), [file1.path, file2.path, file3.path]); // Previous program shouldnt be updated @@ -483,7 +498,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); host.reloadFS([file1, file2, configFile]); @@ -508,7 +523,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); @@ -538,7 +553,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path]); const modifiedConfigFile = { @@ -566,7 +581,7 @@ namespace ts.tscWatch { content: JSON.stringify({ compilerOptions: {} }) }; const host = createWatchedSystem([file1, file2, config]); - const watch = createWatchWithConfig(config.path, host); + const watch = createWatchModeWithConfigFile(config.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path]); @@ -588,7 +603,7 @@ namespace ts.tscWatch { content: "{" }; const host = createWatchedSystem([file1, corruptedConfig]); - const watch = createWatchWithConfig(corruptedConfig.path, host); + const watch = createWatchModeWithConfigFile(corruptedConfig.path, host); checkProgramActualFiles(watch(), [file1.path]); }); @@ -638,7 +653,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const watch = createWatchWithConfig(config1.path, host); + const watch = createWatchModeWithConfigFile(config1.path, host); checkProgramActualFiles(watch(), [libES5.path, app.path]); @@ -663,7 +678,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([f, config]); - const watch = createWatchWithConfig(config.path, host); + const watch = createWatchModeWithConfigFile(config.path, host); checkProgramActualFiles(watch(), [f.path]); }); @@ -677,7 +692,7 @@ namespace ts.tscWatch { content: "import * as T from './moduleFile'; T.bar();" }; const host = createWatchedSystem([moduleFile, file1, libFile]); - createWatchModeWithoutConfigFile([file1.path], {}, host); + createWatchModeWithoutConfigFile([file1.path], host); const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n"; checkOutputDoesNotContain(host, [error]); @@ -709,7 +724,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`; checkOutputDoesNotContain(host, [error]); @@ -745,7 +760,7 @@ namespace ts.tscWatch { path: "/a/c" }; const host = createWatchedSystem([f1, config, node, cwd], { currentDirectory: cwd.path }); - const watch = createWatchWithConfig(config.path, host); + const watch = createWatchModeWithConfigFile(config.path, host); checkProgramActualFiles(watch(), [f1.path, node.path]); }); @@ -760,7 +775,7 @@ namespace ts.tscWatch { content: "import * as T from './moduleFile'; T.bar();" }; const host = createWatchedSystem([file1, libFile]); - createWatchModeWithoutConfigFile([file1.path], {}, host); + createWatchModeWithoutConfigFile([file1.path], host); const error = `a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.${host.newLine}`; checkOutputContains(host, [error]); @@ -787,7 +802,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); checkOutputContains(host, [ `a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`, `a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}` @@ -807,7 +822,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); checkOutputDoesNotContain(host, [ `a/b/tsconfig.json(3,29): error TS5023: Unknown compiler option \'foo\'.${host.newLine}`, `a/b/tsconfig.json(4,29): error TS5023: Unknown compiler option \'allowJS\'.${host.newLine}` @@ -827,7 +842,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); const error = `a/b/tsconfig.json(3,25): error TS5023: Unknown compiler option 'haha'.${host.newLine}`; checkOutputDoesNotContain(host, [error]); @@ -863,7 +878,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile, libFile]); - const watch = createWatchWithConfig(configFile.path, host); + const watch = createWatchModeWithConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [libFile.path]); }); @@ -889,7 +904,7 @@ namespace ts.tscWatch { content: `export const x: number` }; const host = createWatchedSystem([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); - const watch = createWatchWithConfig(config.path, host); + const watch = createWatchModeWithConfigFile(config.path, host); checkProgramActualFiles(watch(), [t1.path, t2.path]); }); @@ -900,7 +915,7 @@ namespace ts.tscWatch { content: "let x = 1" }; const host = createWatchedSystem([f, libFile]); - const watch = createWatchModeWithoutConfigFile([f.path], { allowNonTsExtensions: true }, host); + const watch = createWatchModeWithoutConfigFile([f.path], host, { allowNonTsExtensions: true }); checkProgramActualFiles(watch(), [f.path, libFile.path]); }); @@ -934,7 +949,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, libFile, configFile]); - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); checkOutputContains(host, errors(line)); checkOutputDoesNotContain(host, errors(line - 2)); host.clearOutput(); @@ -985,7 +1000,7 @@ namespace ts.tscWatch { const files = [f1, f2, config, libFile]; host.reloadFS(files); - createWatchWithConfig(config.path, host); + createWatchModeWithConfigFile(config.path, host); const allEmittedLines = getEmittedLines(files); checkOutputContains(host, allEmittedLines); @@ -1073,7 +1088,7 @@ namespace ts.tscWatch { host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); // Initial compile - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); if (firstCompilationEmitFiles) { checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); } @@ -1384,11 +1399,11 @@ namespace ts.tscWatch { // Initial compile if (configFile) { - createWatchWithConfig(configFile.path, host); + createWatchModeWithConfigFile(configFile.path, host); } else { // First file as the root - createWatchModeWithoutConfigFile([files[0].path], { listEmittedFiles: true }, host); + createWatchModeWithoutConfigFile([files[0].path], host, { listEmittedFiles: true }); } checkOutputContains(host, allEmittedFiles); From 031a63762ff5e12d51d2c716339b2abb1efffdc8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 3 Aug 2017 21:10:26 -0700 Subject: [PATCH 044/144] Switch back to have tsc.ts the only file thats different in tsc.js generation now that api has tests --- src/compiler/tsc.ts | 392 +++++++++++++++++++++++++- src/compiler/tscLib.ts | 391 ------------------------- src/compiler/tsconfig.json | 1 - src/harness/unittests/tscWatchMode.ts | 2 +- 4 files changed, 392 insertions(+), 394 deletions(-) delete mode 100644 src/compiler/tscLib.ts diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 3a672fca727..b89a41a2d23 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -1,4 +1,394 @@ -/// +/// +/// +/// + +namespace ts { + interface Statistic { + name: string; + value: string; + } + + function countLines(program: Program): number { + let count = 0; + forEach(program.getSourceFiles(), file => { + count += getLineStarts(file).length; + }); + return count; + } + + function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText; + } + + let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply); + function udpateReportDiagnostic(options: CompilerOptions) { + if (options.pretty) { + reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext); + } + } + + function padLeft(s: string, length: number) { + while (s.length < length) { + s = " " + s; + } + return s; + } + + function padRight(s: string, length: number) { + while (s.length < length) { + s = s + " "; + } + + return s; + } + + function isJSONSupported() { + return typeof JSON === "object" && typeof JSON.parse === "function"; + } + + export function executeCommandLine(args: string[]): void { + const commandLine = parseCommandLine(args); + + // Configuration file name (if any) + let configFileName: string; + if (commandLine.options.locale) { + if (!isJSONSupported()) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale")); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors); + } + + // If there are any errors due to command line parsing and/or + // setting up localization, report them and quit. + if (commandLine.errors.length > 0) { + reportDiagnostics(commandLine.errors, reportDiagnostic); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + if (commandLine.options.init) { + writeConfigFile(commandLine.options, commandLine.fileNames); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.version) { + printVersion(); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.help || commandLine.options.all) { + printVersion(); + printHelp(commandLine.options.all); + return sys.exit(ExitStatus.Success); + } + + if (commandLine.options.project) { + if (!isJSONSupported()) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project")); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + if (commandLine.fileNames.length !== 0) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line)); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + const fileOrDirectory = normalizePath(commandLine.options.project); + if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) { + configFileName = combinePaths(fileOrDirectory, "tsconfig.json"); + if (!sys.fileExists(configFileName)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project)); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + else { + configFileName = fileOrDirectory; + if (!sys.fileExists(configFileName)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project)); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + } + else if (commandLine.fileNames.length === 0 && isJSONSupported()) { + const searchPath = normalizePath(sys.getCurrentDirectory()); + configFileName = findConfigFile(searchPath, sys.fileExists); + } + + if (commandLine.fileNames.length === 0 && !configFileName) { + printVersion(); + printHelp(commandLine.options.all); + return sys.exit(ExitStatus.Success); + } + + const commandLineOptions = commandLine.options; + if (configFileName) { + const reportWatchDiagnostic = createWatchDiagnosticReporter(); + const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic); + udpateReportDiagnostic(configParseResult.options); + if (isWatchSet(configParseResult.options)) { + reportWatchModeWithoutSysSupport(); + createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic)); + } + else { + performCompilation(configParseResult.fileNames, configParseResult.options); + } + } + else { + udpateReportDiagnostic(commandLineOptions); + if (isWatchSet(commandLineOptions)) { + reportWatchModeWithoutSysSupport(); + createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost()); + } + else { + performCompilation(commandLine.fileNames, commandLineOptions); + } + } + } + + function reportWatchModeWithoutSysSupport() { + if (!sys.watchFile || !sys.watchDirectory) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch")); + sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + } + + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { + const compilerHost = createCompilerHost(compilerOptions); + enableStatistics(compilerOptions); + + const program = createProgram(rootFileNames, compilerOptions, compilerHost); + const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); + + reportStatistics(program); + return sys.exit(exitStatus); + } + + function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) { + const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); + watchingHost.beforeCompile = enableStatistics; + const afterCompile = watchingHost.afterCompile; + watchingHost.afterCompile = (host, program, builder) => { + afterCompile(host, program, builder); + reportStatistics(program); + }; + return watchingHost; + } + + function enableStatistics(compilerOptions: CompilerOptions) { + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { + performance.enable(); + } + } + + function reportStatistics(program: Program) { + let statistics: Statistic[]; + const compilerOptions = program.getCompilerOptions(); + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { + statistics = []; + const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; + reportCountStatistic("Files", program.getSourceFiles().length); + reportCountStatistic("Lines", countLines(program)); + reportCountStatistic("Nodes", program.getNodeCount()); + reportCountStatistic("Identifiers", program.getIdentifierCount()); + reportCountStatistic("Symbols", program.getSymbolCount()); + reportCountStatistic("Types", program.getTypeCount()); + + if (memoryUsed >= 0) { + reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); + } + + const programTime = performance.getDuration("Program"); + const bindTime = performance.getDuration("Bind"); + const checkTime = performance.getDuration("Check"); + const emitTime = performance.getDuration("Emit"); + if (compilerOptions.extendedDiagnostics) { + performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + } + else { + // Individual component times. + // Note: To match the behavior of previous versions of the compiler, the reported parse time includes + // I/O read time and processing time for triple-slash references and module imports, and the reported + // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. + reportTimeStatistic("I/O read", performance.getDuration("I/O Read")); + reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); + reportTimeStatistic("Parse time", programTime); + reportTimeStatistic("Bind time", bindTime); + reportTimeStatistic("Check time", checkTime); + reportTimeStatistic("Emit time", emitTime); + } + reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + reportStatistics(); + + performance.disable(); + } + + function reportStatistics() { + let nameSize = 0; + let valueSize = 0; + for (const { name, value } of statistics) { + if (name.length > nameSize) { + nameSize = name.length; + } + + if (value.length > valueSize) { + valueSize = value.length; + } + } + + for (const { name, value } of statistics) { + sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); + } + } + + function reportStatisticalValue(name: string, value: string) { + statistics.push({ name, value }); + } + + function reportCountStatistic(name: string, count: number) { + reportStatisticalValue(name, "" + count); + } + + function reportTimeStatistic(name: string, time: number) { + reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); + } + } + + function printVersion() { + sys.write(getDiagnosticText(Diagnostics.Version_0, ts.version) + sys.newLine); + } + + function printHelp(showAllOptions: boolean) { + const output: string[] = []; + + // We want to align our "syntax" and "examples" commands to a certain margin. + const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; + const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; + let marginLength = Math.max(syntaxLength, examplesLength); + + // Build up the syntactic skeleton. + let syntax = makePadding(marginLength - syntaxLength); + syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; + + output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); + output.push(sys.newLine + sys.newLine); + + // Build up the list of examples. + const padding = makePadding(marginLength); + output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); + output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); + output.push(padding + "tsc @args.txt" + sys.newLine); + output.push(sys.newLine); + + output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); + + // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") + const optsList = showAllOptions ? + optionDeclarations.slice().sort((a, b) => compareValues(a.name.toLowerCase(), b.name.toLowerCase())) : + filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView); + + // We want our descriptions to align at the same column in our output, + // so we keep track of the longest option usage string. + marginLength = 0; + const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. + const descriptionColumn: string[] = []; + + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind + + for (let i = 0; i < optsList.length; i++) { + const option = optsList[i]; + + // If an option lacks a description, + // it is not officially supported. + if (!option.description) { + continue; + } + + let usageText = " "; + if (option.shortName) { + usageText += "-" + option.shortName; + usageText += getParamType(option); + usageText += ", "; + } + + usageText += "--" + option.name; + usageText += getParamType(option); + + usageColumn.push(usageText); + let description: string; + + if (option.name === "lib") { + description = getDiagnosticText(option.description); + const element = (option).element; + const typeMap = >element.type; + optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); + } + else { + description = getDiagnosticText(option.description); + } + + descriptionColumn.push(description); + + // Set the new margin for the description column if necessary. + marginLength = Math.max(usageText.length, marginLength); + } + + // Special case that can't fit in the loop. + const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; + usageColumn.push(usageText); + descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); + marginLength = Math.max(usageText.length, marginLength); + + // Print out each row, aligning all the descriptions on the same column. + for (let i = 0; i < usageColumn.length; i++) { + const usage = usageColumn[i]; + const description = descriptionColumn[i]; + const kindsList = optionsDescriptionMap.get(description); + output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); + + if (kindsList) { + output.push(makePadding(marginLength + 4)); + for (const kind of kindsList) { + output.push(kind + " "); + } + output.push(sys.newLine); + } + } + + for (const line of output) { + sys.write(line); + } + return; + + function getParamType(option: CommandLineOption) { + if (option.paramType !== undefined) { + return " " + getDiagnosticText(option.paramType); + } + return ""; + } + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + } + + function writeConfigFile(options: CompilerOptions, fileNames: string[]) { + const currentDirectory = sys.getCurrentDirectory(); + const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); + if (sys.fileExists(file)) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file)); + } + else { + sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file)); + } + + return; + } +} + +if (ts.Debug.isDebugging) { + ts.Debug.enableDebugInfo(); +} if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { ts.sys.tryEnableSourceMapsForHost(); diff --git a/src/compiler/tscLib.ts b/src/compiler/tscLib.ts deleted file mode 100644 index bd4a64d8899..00000000000 --- a/src/compiler/tscLib.ts +++ /dev/null @@ -1,391 +0,0 @@ -/// -/// -/// - -namespace ts { - interface Statistic { - name: string; - value: string; - } - - function countLines(program: Program): number { - let count = 0; - forEach(program.getSourceFiles(), file => { - count += getLineStarts(file).length; - }); - return count; - } - - function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText; - } - - let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply); - function udpateReportDiagnostic(options: CompilerOptions) { - if (options.pretty) { - reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext); - } - } - - function padLeft(s: string, length: number) { - while (s.length < length) { - s = " " + s; - } - return s; - } - - function padRight(s: string, length: number) { - while (s.length < length) { - s = s + " "; - } - - return s; - } - - function isJSONSupported() { - return typeof JSON === "object" && typeof JSON.parse === "function"; - } - - export function executeCommandLine(args: string[]): void { - const commandLine = parseCommandLine(args); - - // Configuration file name (if any) - let configFileName: string; - if (commandLine.options.locale) { - if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale")); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors); - } - - // If there are any errors due to command line parsing and/or - // setting up localization, report them and quit. - if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, reportDiagnostic); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - if (commandLine.options.init) { - writeConfigFile(commandLine.options, commandLine.fileNames); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.version) { - printVersion(); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.help || commandLine.options.all) { - printVersion(); - printHelp(commandLine.options.all); - return sys.exit(ExitStatus.Success); - } - - if (commandLine.options.project) { - if (!isJSONSupported()) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project")); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - if (commandLine.fileNames.length !== 0) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line)); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - const fileOrDirectory = normalizePath(commandLine.options.project); - if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) { - configFileName = combinePaths(fileOrDirectory, "tsconfig.json"); - if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project)); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - else { - configFileName = fileOrDirectory; - if (!sys.fileExists(configFileName)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project)); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - } - else if (commandLine.fileNames.length === 0 && isJSONSupported()) { - const searchPath = normalizePath(sys.getCurrentDirectory()); - configFileName = findConfigFile(searchPath, sys.fileExists); - } - - if (commandLine.fileNames.length === 0 && !configFileName) { - printVersion(); - printHelp(commandLine.options.all); - return sys.exit(ExitStatus.Success); - } - - const commandLineOptions = commandLine.options; - if (configFileName) { - const reportWatchDiagnostic = createWatchDiagnosticReporter(); - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic); - udpateReportDiagnostic(configParseResult.options); - if (isWatchSet(configParseResult.options)) { - reportWatchModeWithoutSysSupport(); - createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic)); - } - else { - performCompilation(configParseResult.fileNames, configParseResult.options); - } - } - else { - udpateReportDiagnostic(commandLineOptions); - if (isWatchSet(commandLineOptions)) { - reportWatchModeWithoutSysSupport(); - createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost()); - } - else { - performCompilation(commandLine.fileNames, commandLineOptions); - } - } - } - - function reportWatchModeWithoutSysSupport() { - if (!sys.watchFile || !sys.watchDirectory) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch")); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - } - - function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { - const compilerHost = createCompilerHost(compilerOptions); - enableStatistics(compilerOptions); - - const program = createProgram(rootFileNames, compilerOptions, compilerHost); - const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); - - reportStatistics(program); - return sys.exit(exitStatus); - } - - function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) { - const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); - watchingHost.beforeCompile = enableStatistics; - const afterCompile = watchingHost.afterCompile; - watchingHost.afterCompile = (host, program, builder) => { - afterCompile(host, program, builder); - reportStatistics(program); - }; - return watchingHost; - } - - function enableStatistics(compilerOptions: CompilerOptions) { - if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { - performance.enable(); - } - } - - function reportStatistics(program: Program) { - let statistics: Statistic[]; - const compilerOptions = program.getCompilerOptions(); - if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { - statistics = []; - const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; - reportCountStatistic("Files", program.getSourceFiles().length); - reportCountStatistic("Lines", countLines(program)); - reportCountStatistic("Nodes", program.getNodeCount()); - reportCountStatistic("Identifiers", program.getIdentifierCount()); - reportCountStatistic("Symbols", program.getSymbolCount()); - reportCountStatistic("Types", program.getTypeCount()); - - if (memoryUsed >= 0) { - reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); - } - - const programTime = performance.getDuration("Program"); - const bindTime = performance.getDuration("Bind"); - const checkTime = performance.getDuration("Check"); - const emitTime = performance.getDuration("Emit"); - if (compilerOptions.extendedDiagnostics) { - performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); - } - else { - // Individual component times. - // Note: To match the behavior of previous versions of the compiler, the reported parse time includes - // I/O read time and processing time for triple-slash references and module imports, and the reported - // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. - reportTimeStatistic("I/O read", performance.getDuration("I/O Read")); - reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); - reportTimeStatistic("Parse time", programTime); - reportTimeStatistic("Bind time", bindTime); - reportTimeStatistic("Check time", checkTime); - reportTimeStatistic("Emit time", emitTime); - } - reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); - reportStatistics(); - - performance.disable(); - } - - function reportStatistics() { - let nameSize = 0; - let valueSize = 0; - for (const { name, value } of statistics) { - if (name.length > nameSize) { - nameSize = name.length; - } - - if (value.length > valueSize) { - valueSize = value.length; - } - } - - for (const { name, value } of statistics) { - sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); - } - } - - function reportStatisticalValue(name: string, value: string) { - statistics.push({ name, value }); - } - - function reportCountStatistic(name: string, count: number) { - reportStatisticalValue(name, "" + count); - } - - function reportTimeStatistic(name: string, time: number) { - reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); - } - } - - function printVersion() { - sys.write(getDiagnosticText(Diagnostics.Version_0, ts.version) + sys.newLine); - } - - function printHelp(showAllOptions: boolean) { - const output: string[] = []; - - // We want to align our "syntax" and "examples" commands to a certain margin. - const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; - const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; - let marginLength = Math.max(syntaxLength, examplesLength); - - // Build up the syntactic skeleton. - let syntax = makePadding(marginLength - syntaxLength); - syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; - - output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); - output.push(sys.newLine + sys.newLine); - - // Build up the list of examples. - const padding = makePadding(marginLength); - output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); - output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); - output.push(padding + "tsc @args.txt" + sys.newLine); - output.push(sys.newLine); - - output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); - - // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") - const optsList = showAllOptions ? - optionDeclarations.slice().sort((a, b) => compareValues(a.name.toLowerCase(), b.name.toLowerCase())) : - filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView); - - // We want our descriptions to align at the same column in our output, - // so we keep track of the longest option usage string. - marginLength = 0; - const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. - const descriptionColumn: string[] = []; - - const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind - - for (let i = 0; i < optsList.length; i++) { - const option = optsList[i]; - - // If an option lacks a description, - // it is not officially supported. - if (!option.description) { - continue; - } - - let usageText = " "; - if (option.shortName) { - usageText += "-" + option.shortName; - usageText += getParamType(option); - usageText += ", "; - } - - usageText += "--" + option.name; - usageText += getParamType(option); - - usageColumn.push(usageText); - let description: string; - - if (option.name === "lib") { - description = getDiagnosticText(option.description); - const element = (option).element; - const typeMap = >element.type; - optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); - } - else { - description = getDiagnosticText(option.description); - } - - descriptionColumn.push(description); - - // Set the new margin for the description column if necessary. - marginLength = Math.max(usageText.length, marginLength); - } - - // Special case that can't fit in the loop. - const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; - usageColumn.push(usageText); - descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); - marginLength = Math.max(usageText.length, marginLength); - - // Print out each row, aligning all the descriptions on the same column. - for (let i = 0; i < usageColumn.length; i++) { - const usage = usageColumn[i]; - const description = descriptionColumn[i]; - const kindsList = optionsDescriptionMap.get(description); - output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); - - if (kindsList) { - output.push(makePadding(marginLength + 4)); - for (const kind of kindsList) { - output.push(kind + " "); - } - output.push(sys.newLine); - } - } - - for (const line of output) { - sys.write(line); - } - return; - - function getParamType(option: CommandLineOption) { - if (option.paramType !== undefined) { - return " " + getDiagnosticText(option.paramType); - } - return ""; - } - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - } - - function writeConfigFile(options: CompilerOptions, fileNames: string[]) { - const currentDirectory = sys.getCurrentDirectory(); - const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); - if (sys.fileExists(file)) { - reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file)); - } - else { - sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file)); - } - - return; - } -} - -if (ts.Debug.isDebugging) { - ts.Debug.enableDebugInfo(); -} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index fd92a6008cd..08d50e5eb82 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -39,7 +39,6 @@ "builder.ts", "watchedProgram.ts", "commandLineParser.ts", - "tscLib.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" ] diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 608fc30c6b9..7c535e40687 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1,5 +1,5 @@ /// -/// +/// /// namespace ts.tscWatch { From 0d5e6c9de57d1ea447c5505d6a676473725411ac Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 4 Aug 2017 01:14:54 -0700 Subject: [PATCH 045/144] Use cache for module resolution even in watch mode --- src/compiler/resolutionCache.ts | 198 ++++++++++++++++++++++++++++++++ src/compiler/tsconfig.json | 1 + src/compiler/watchedProgram.ts | 33 +++--- src/server/lsHost.ts | 164 +------------------------- src/server/project.ts | 25 +++- 5 files changed, 244 insertions(+), 177 deletions(-) create mode 100644 src/compiler/resolutionCache.ts diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts new file mode 100644 index 00000000000..f92464e5a3e --- /dev/null +++ b/src/compiler/resolutionCache.ts @@ -0,0 +1,198 @@ +/// +/// + +namespace ts { + export interface ResolutionCache { + setModuleResolutionHost(host: ModuleResolutionHost): void; + startRecordingFilesWithChangedResolutions(): void; + finishRecordingFilesWithChangedResolutions(): Path[]; + resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[]; + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + invalidateResolutionOfDeletedFile(filePath: Path): void; + clear(): void; + } + + type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; + type ResolverWithGlobalCache = (primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost) => ResolvedModuleWithFailedLookupLocations | undefined; + + /*@internal*/ + export function resolveWithGlobalCache(primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string | undefined, projectName: string): ResolvedModuleWithFailedLookupLocations | undefined { + if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) { + // otherwise try to load typings from @types + + // create different collection of failed lookup locations for second pass + // if it will fail and we've already found something during the first pass - we don't want to pollute its results + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); + if (resolvedModule) { + return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) }; + } + } + } + + /*@internal*/ + export function createResolutionCache( + toPath: (fileName: string) => Path, + getCompilerOptions: () => CompilerOptions, + resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache { + + let host: ModuleResolutionHost; + let filesWithChangedSetOfUnresolvedImports: Path[]; + const resolvedModuleNames = createMap>(); + const resolvedTypeReferenceDirectives = createMap>(); + + return { + setModuleResolutionHost, + startRecordingFilesWithChangedResolutions, + finishRecordingFilesWithChangedResolutions, + resolveModuleNames, + resolveTypeReferenceDirectives, + invalidateResolutionOfDeletedFile, + clear + }; + + function setModuleResolutionHost(updatedHost: ModuleResolutionHost) { + host = updatedHost; + } + + function clear() { + resolvedModuleNames.clear(); + resolvedTypeReferenceDirectives.clear(); + } + + function startRecordingFilesWithChangedResolutions() { + filesWithChangedSetOfUnresolvedImports = []; + } + + function finishRecordingFilesWithChangedResolutions() { + const collected = filesWithChangedSetOfUnresolvedImports; + filesWithChangedSetOfUnresolvedImports = undefined; + return collected; + } + + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); + // return result immediately only if it is .ts, .tsx or .d.ts + // otherwise try to load typings from @types + return (resolveWithGlobalCache && resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host)) || primaryResult; + } + + function resolveNamesWithLocalCache( + names: string[], + containingFile: string, + cache: Map>, + loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, + getResult: (s: T) => R, + getResultFileName: (result: R) => string | undefined, + logChanges: boolean): R[] { + + const path = toPath(containingFile); + const currentResolutionsInFile = cache.get(path); + + const newResolutions: Map = createMap(); + const resolvedModules: R[] = []; + const compilerOptions = getCompilerOptions(); + + for (const name of names) { + // check if this is a duplicate entry in the list + let resolution = newResolutions.get(name); + if (!resolution) { + const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); + if (moduleResolutionIsValid(existingResolution)) { + // ok, it is safe to use existing name resolution results + resolution = existingResolution; + } + else { + resolution = loader(name, containingFile, compilerOptions, host); + newResolutions.set(name, resolution); + } + if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { + filesWithChangedSetOfUnresolvedImports.push(path); + // reset log changes to avoid recording the same file multiple times + logChanges = false; + } + } + + Debug.assert(resolution !== undefined); + + resolvedModules.push(getResult(resolution)); + } + + // replace old results with a new one + cache.set(path, newResolutions); + return resolvedModules; + + function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean { + if (oldResolution === newResolution) { + return true; + } + if (!oldResolution || !newResolution || oldResolution.isInvalidated) { + return false; + } + const oldResult = getResult(oldResolution); + const newResult = getResult(newResolution); + if (oldResult === newResult) { + return true; + } + if (!oldResult || !newResult) { + return false; + } + return getResultFileName(oldResult) === getResultFileName(newResult); + } + + function moduleResolutionIsValid(resolution: T): boolean { + if (!resolution || resolution.isInvalidated) { + return false; + } + + const result = getResult(resolution); + if (result) { + return true; + } + + // consider situation if we have no candidate locations as valid resolution. + // after all there is no point to invalidate it if we have no idea where to look for the module. + return resolution.failedLookupLocations.length === 0; + } + } + + + function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { + return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, + m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false); + } + + function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { + return resolveNamesWithLocalCache(moduleNames, containingFile, resolvedModuleNames, resolveModuleName, + m => m.resolvedModule, r => r.resolvedFileName, logChanges); + } + + function invalidateResolutionCacheOfDeletedFile( + deletedFilePath: Path, + cache: Map>, + getResult: (s: T) => R, + getResultFileName: (result: R) => string | undefined) { + cache.forEach((value, path) => { + if (path === deletedFilePath) { + cache.delete(path); + } + else if (value) { + value.forEach((resolution) => { + if (resolution && !resolution.isInvalidated) { + const result = getResult(resolution); + if (result) { + if (getResultFileName(result) === deletedFilePath) { + resolution.isInvalidated = true; + } + } + } + }); + } + }); + } + + function invalidateResolutionOfDeletedFile(filePath: Path) { + invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); + invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); + } + } +} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 08d50e5eb82..1001f51f330 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -37,6 +37,7 @@ "emitter.ts", "program.ts", "builder.ts", + "resolutionCache.ts", "watchedProgram.ts", "commandLineParser.ts", "tsc.ts", diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 22625282a8d..8bf5c542456 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -1,5 +1,6 @@ /// /// +/// namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; @@ -254,6 +255,7 @@ namespace ts { let timerToUpdateProgram: any; // timer callback to recompile the program const sourceFilesCache = createMap(); // Cache that stores the source file and version info + watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; @@ -268,6 +270,9 @@ namespace ts { const currentDirectory = host.getCurrentDirectory(); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + // Cache for the module resolution + const resolutionCache = createResolutionCache(fileName => toPath(fileName), () => compilerOptions); + // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); @@ -287,6 +292,10 @@ namespace ts { // Create the compiler host const compilerHost = createWatchedCompilerHost(compilerOptions); + resolutionCache.setModuleResolutionHost(compilerHost); + if (changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { + resolutionCache.clear(); + } beforeCompile(compilerOptions); // Compile the program @@ -321,22 +330,18 @@ namespace ts { getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", getDirectories: (path: string) => host.getDirectories(path), realpath, - onReleaseOldSourceFile, + resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), + resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false), + onReleaseOldSourceFile }; + } - // TODO: cache module resolution - // if (host.resolveModuleNames) { - // compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); - // } - // if (host.resolveTypeReferenceDirectives) { - // compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { - // return host.resolveTypeReferenceDirectives(typeReferenceDirectiveNames, containingFile); - // }; - // } + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } function fileExists(fileName: string) { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const path = toPath(fileName); const hostSourceFileInfo = sourceFilesCache.get(path); if (hostSourceFileInfo !== undefined) { return !isString(hostSourceFileInfo); @@ -350,7 +355,7 @@ namespace ts { } function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - return getVersionedSourceFileByPath(fileName, toPath(fileName, currentDirectory, getCanonicalFileName), languageVersion, onError, shouldCreateNewSourceFile); + return getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile); } function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { @@ -418,6 +423,7 @@ namespace ts { if (hostSourceFile !== undefined) { if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); + resolutionCache.invalidateResolutionOfDeletedFile(path); } sourceFilesCache.delete(path); } @@ -501,6 +507,7 @@ namespace ts { if (hostSourceFile) { // Update the cache if (eventKind === FileWatcherEventKind.Deleted) { + resolutionCache.invalidateResolutionOfDeletedFile(path); if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); sourceFilesCache.set(path, (hostSourceFile.version++).toString()); @@ -574,7 +581,7 @@ namespace ts { function onFileAddOrRemoveInWatchedDirectory(fileName: string) { Debug.assert(!!configFileName); - const path = toPath(fileName, currentDirectory, getCanonicalFileName); + const path = toPath(fileName); // Since the file existance changed, update the sourceFiles cache updateCachedSystem(fileName, path); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 692547e4915..274c3c3aef6 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -1,6 +1,7 @@ /// /// /// +/// namespace ts.server { export class CachedServerHost implements ServerHost { @@ -103,15 +104,10 @@ namespace ts.server { } - type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; export class LSHost implements LanguageServiceHost, ModuleResolutionHost { - private compilationSettings: CompilerOptions; - private readonly resolvedModuleNames = createMap>(); - private readonly resolvedTypeReferenceDirectives = createMap>(); + /*@internal*/ + compilationSettings: CompilerOptions; - private filesWithChangedSetOfUnresolvedImports: Path[]; - - private resolveModuleName: typeof resolveModuleName; readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; /** @@ -130,25 +126,6 @@ namespace ts.server { this.trace = s => host.trace(s); } - this.resolveModuleName = (moduleName, containingFile, compilerOptions, host) => { - const globalCache = this.project.getTypeAcquisition().enable - ? this.project.projectService.typingsInstaller.globalTypingsCacheLocation - : undefined; - const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host); - // return result immediately only if it is .ts, .tsx or .d.ts - if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) { - // otherwise try to load typings from @types - - // create different collection of failed lookup locations for second pass - // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, this.project.getProjectName(), compilerOptions, host, globalCache); - if (resolvedModule) { - return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) }; - } - } - return primaryResult; - }; - if (this.host.realpath) { this.realpath = path => this.host.realpath(path); } @@ -156,99 +133,9 @@ namespace ts.server { dispose() { this.project = undefined; - this.resolveModuleName = undefined; this.host = undefined; } - public startRecordingFilesWithChangedResolutions() { - this.filesWithChangedSetOfUnresolvedImports = []; - } - - public finishRecordingFilesWithChangedResolutions() { - const collected = this.filesWithChangedSetOfUnresolvedImports; - this.filesWithChangedSetOfUnresolvedImports = undefined; - return collected; - } - - private resolveNamesWithLocalCache( - names: string[], - containingFile: string, - cache: Map>, - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, - getResult: (s: T) => R, - getResultFileName: (result: R) => string | undefined, - logChanges: boolean): R[] { - - const path = this.project.projectService.toPath(containingFile); - const currentResolutionsInFile = cache.get(path); - - const newResolutions: Map = createMap(); - const resolvedModules: R[] = []; - const compilerOptions = this.getCompilationSettings(); - - for (const name of names) { - // check if this is a duplicate entry in the list - let resolution = newResolutions.get(name); - if (!resolution) { - const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); - if (moduleResolutionIsValid(existingResolution)) { - // ok, it is safe to use existing name resolution results - resolution = existingResolution; - } - else { - resolution = loader(name, containingFile, compilerOptions, this); - newResolutions.set(name, resolution); - } - if (logChanges && this.filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { - this.filesWithChangedSetOfUnresolvedImports.push(path); - // reset log changes to avoid recording the same file multiple times - logChanges = false; - } - } - - Debug.assert(resolution !== undefined); - - resolvedModules.push(getResult(resolution)); - } - - // replace old results with a new one - cache.set(path, newResolutions); - return resolvedModules; - - function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean { - if (oldResolution === newResolution) { - return true; - } - if (!oldResolution || !newResolution || oldResolution.isInvalidated) { - return false; - } - const oldResult = getResult(oldResolution); - const newResult = getResult(newResolution); - if (oldResult === newResult) { - return true; - } - if (!oldResult || !newResult) { - return false; - } - return getResultFileName(oldResult) === getResultFileName(newResult); - } - - function moduleResolutionIsValid(resolution: T): boolean { - if (!resolution || resolution.isInvalidated) { - return false; - } - - const result = getResult(resolution); - if (result) { - return true; - } - - // consider situation if we have no candidate locations as valid resolution. - // after all there is no point to invalidate it if we have no idea where to look for the module. - return resolution.failedLookupLocations.length === 0; - } - } - getNewLine() { return this.host.newLine; } @@ -270,13 +157,11 @@ namespace ts.server { } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, - m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false); + return this.project.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); } resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { - return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, - m => m.resolvedModule, r => r.resolvedFileName, /*logChanges*/ true); + return this.project.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); } getDefaultLibFileName() { @@ -339,44 +224,5 @@ namespace ts.server { getDirectories(path: string): string[] { return this.host.getDirectories(path); } - - notifyFileRemoved(info: ScriptInfo) { - this.invalidateResolutionOfDeletedFile(info, this.resolvedModuleNames, - m => m.resolvedModule, r => r.resolvedFileName); - this.invalidateResolutionOfDeletedFile(info, this.resolvedTypeReferenceDirectives, - m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); - } - - private invalidateResolutionOfDeletedFile( - deletedInfo: ScriptInfo, - cache: Map>, - getResult: (s: T) => R, - getResultFileName: (result: R) => string | undefined) { - cache.forEach((value, path) => { - if (path === deletedInfo.path) { - cache.delete(path); - } - else if (value) { - value.forEach((resolution) => { - if (resolution && !resolution.isInvalidated) { - const result = getResult(resolution); - if (result) { - if (getResultFileName(result) === deletedInfo.path) { - resolution.isInvalidated = true; - } - } - } - }); - } - }); - } - - setCompilationSettings(opt: CompilerOptions) { - if (changesAffectModuleResolution(this.compilationSettings, opt)) { - this.resolvedModuleNames.clear(); - this.resolvedTypeReferenceDirectives.clear(); - } - this.compilationSettings = opt; - } } } diff --git a/src/server/project.ts b/src/server/project.ts index 02e2fb20759..ebad00e03be 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -127,6 +127,9 @@ namespace ts.server { public languageServiceEnabled = true; + /*@internal*/ + resolutionCache: ResolutionCache; + /*@internal*/ lsHost: LSHost; @@ -211,7 +214,14 @@ namespace ts.server { this.setInternalCompilerOptionsForEmittingJsFiles(); this.lsHost = new LSHost(host, this, this.projectService.cancellationToken); - this.lsHost.setCompilationSettings(this.compilerOptions); + this.resolutionCache = createResolutionCache( + fileName => this.projectService.toPath(fileName), + () => this.compilerOptions, + (primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host, + this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName()) + ); + this.lsHost.compilationSettings = this.compilerOptions; + this.resolutionCache.setModuleResolutionHost(this.lsHost); this.languageService = createLanguageService(this.lsHost, this.documentRegistry); @@ -349,6 +359,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.program = undefined; this.builder = undefined; + this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; this.projectErrors = undefined; this.lsHost.dispose(); @@ -518,7 +529,7 @@ namespace ts.server { if (this.isRoot(info)) { this.removeRoot(info); } - this.lsHost.notifyFileRemoved(info); + this.resolutionCache.invalidateResolutionOfDeletedFile(info.path); this.cachedUnresolvedImportsPerFile.remove(info.path); if (detachFromProject) { @@ -573,11 +584,11 @@ namespace ts.server { * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean { - this.lsHost.startRecordingFilesWithChangedResolutions(); + this.resolutionCache.startRecordingFilesWithChangedResolutions(); let hasChanges = this.updateGraphWorker(); - const changedFiles: ReadonlyArray = this.lsHost.finishRecordingFilesWithChangedResolutions() || emptyArray; + const changedFiles: ReadonlyArray = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray; for (const file of changedFiles) { // delete cached information for changed files @@ -759,9 +770,13 @@ namespace ts.server { this.cachedUnresolvedImportsPerFile.clear(); this.lastCachedUnresolvedImportsList = undefined; } + const oldOptions = this.compilerOptions; this.compilerOptions = compilerOptions; this.setInternalCompilerOptionsForEmittingJsFiles(); - this.lsHost.setCompilationSettings(compilerOptions); + if (changesAffectModuleResolution(oldOptions, compilerOptions)) { + this.resolutionCache.clear(); + } + this.lsHost.compilationSettings = this.compilerOptions; this.markAsDirty(); } From 2762232e038a243715589408e810c86d26be75ad Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 4 Aug 2017 01:15:02 -0700 Subject: [PATCH 046/144] Test for the module resolution caching --- src/harness/unittests/tscWatchMode.ts | 145 ++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 7 deletions(-) diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 7c535e40687..c9f6dcc053f 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -68,7 +68,7 @@ namespace ts.tscWatch { function getEmittedLines(files: FileOrFolderEmit[]) { const seen = createMap(); const result: string[] = []; - for (const { output} of files) { + for (const { output } of files) { if (output && !seen.has(output)) { seen.set(output, true); result.push(output); @@ -1367,12 +1367,12 @@ namespace ts.tscWatch { } function getEmittedFiles(files: FileOrFolderEmit[], contents: string[]): EmittedFile[] { return map(contents, (content, index) => { - return { - content, - path: changeExtension(files[index].path, Extension.Js), - shouldBeWritten: true - }; - } + return { + content, + path: changeExtension(files[index].path, Extension.Js), + shouldBeWritten: true + }; + } ); } function verifyEmittedFiles(host: WatchedSystem, emittedFiles: EmittedFile[]) { @@ -1479,4 +1479,135 @@ namespace ts.tscWatch { } }); }); + + describe("tsc-watch module resolution caching", () => { + it("works", () => { + const root = { + path: "/a/d/f0.ts", + content: `import {x} from "f1"` + }; + + const imported = { + path: "/a/f1.ts", + content: `foo()` + }; + + const f1IsNotModule = `a/d/f0.ts(1,17): error TS2306: File '${imported.path}' is not a module.\n`; + const cannotFindFoo = `a/f1.ts(1,1): error TS2304: Cannot find name 'foo'.\n`; + const cannotAssignValue = "a/d/f0.ts(2,21): error TS2322: Type '1' is not assignable to type 'string'.\n"; + + const files = [root, imported, libFile]; + const host = createWatchedSystem(files); + createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + + // ensure that imported file was found + checkOutputContains(host, [f1IsNotModule, cannotFindFoo]); + host.clearOutput(); + + const originalFileExists = host.fileExists; + { + const newContent = `import {x} from "f1" + var x: string = 1;`; + root.content = newContent; + host.reloadFS(files); + + // patch fileExists to make sure that disk is not touched + host.fileExists = notImplemented; + + // trigger synchronization to make sure that import will be fetched from the cache + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + checkOutputContains(host, [f1IsNotModule, cannotAssignValue]); + host.clearOutput(); + } + { + let fileExistsIsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsIsCalled = true; + assert.isTrue(fileName.indexOf("/f2.") !== -1); + return originalFileExists.call(host, fileName); + }; + + root.content = `import {x} from "f2"`; + host.reloadFS(files); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + host.runQueuedTimeoutCallbacks(); + + // ensure file has correct number of errors after edit + const cannotFindModuleF2 = `a/d/f0.ts(1,17): error TS2307: Cannot find module 'f2'.\n`; + checkOutputContains(host, [cannotFindModuleF2]); + host.clearOutput(); + + assert.isTrue(fileExistsIsCalled); + } + { + let fileExistsCalled = false; + host.fileExists = (fileName): boolean => { + if (fileName === "lib.d.ts") { + return false; + } + fileExistsCalled = true; + assert.isTrue(fileName.indexOf("/f1.") !== -1); + return originalFileExists.call(host, fileName); + }; + + const newContent = `import {x} from "f1"`; + root.content = newContent; + + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + checkOutputContains(host, [f1IsNotModule, cannotFindFoo]); + assert.isTrue(fileExistsCalled); + } + }); + + it("loads missing files from disk", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const host = createWatchedSystem(files); + const originalFileExists = host.fileExists; + + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + + return originalFileExists.call(host, fileName); + }; + + createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + + const barNotFound = `a/foo.ts(1,17): error TS2307: Cannot find module 'bar'.\n`; + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputContains(host, [barNotFound]); + host.clearOutput(); + + fileExistsCalledForBar = false; + root.content = `import {y} from "bar"`; + host.reloadFS(files.concat(imported)); + + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputDoesNotContain(host, [barNotFound]); + }); + }); } From 65a6ee07e981d996c12e4df66214469fc1e56471 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 4 Aug 2017 23:55:13 -0700 Subject: [PATCH 047/144] Add test that fails because we dont watch module resolutions failed paths --- src/harness/unittests/tscWatchMode.ts | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index c9f6dcc053f..8d6587fc2b3 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1609,5 +1609,52 @@ namespace ts.tscWatch { assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); checkOutputDoesNotContain(host, [barNotFound]); }); + + it("should compile correctly when resolved module goes missing and then comes back (module is not part of the root)", () => { + const root = { + path: `/a/foo.ts`, + content: `import {x} from "bar"` + }; + + const imported = { + path: `/a/bar.d.ts`, + content: `export const y = 1;` + }; + + const files = [root, libFile]; + const filesWithImported = files.concat(imported); + const host = createWatchedSystem(filesWithImported); + const originalFileExists = host.fileExists; + let fileExistsCalledForBar = false; + host.fileExists = fileName => { + if (fileName === "lib.d.ts") { + return false; + } + if (!fileExistsCalledForBar) { + fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; + } + return originalFileExists.call(host, fileName); + }; + + createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + + const barNotFound = `a/foo.ts(1,17): error TS2307: Cannot find module 'bar'.\n`; + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); + checkOutputDoesNotContain(host, [barNotFound]); + host.clearOutput(); + + fileExistsCalledForBar = false; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputContains(host, [barNotFound]); + host.clearOutput(); + + fileExistsCalledForBar = false; + host.reloadFS(filesWithImported); + host.checkTimeoutQueueLengthAndRun(1); + assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); + checkOutputDoesNotContain(host, [barNotFound]); + }); }); } From d55150cbd34cbfab15d91d9a18c17e3cc2a92ff6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 4 Aug 2017 23:09:11 -0700 Subject: [PATCH 048/144] Implementation of watching the failed lookup locations --- src/compiler/resolutionCache.ts | 88 ++++++++++++++++++++++++++++++++- src/compiler/watchedProgram.ts | 13 ++++- src/server/project.ts | 4 ++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index f92464e5a3e..f2d20ab19a2 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -33,13 +33,20 @@ namespace ts { export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, + clearProgramAndScheduleUpdate: () => void, + watchForFailedLookupLocation: (fileName: string, callback: FileWatcherCallback) => FileWatcher, + log: (s: string) => void, resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache { let host: ModuleResolutionHost; let filesWithChangedSetOfUnresolvedImports: Path[]; + const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); + type FailedLookupLocationsWatcher = { fileWatcher: FileWatcher; refCount: number }; + const failedLookupLocationsWatches = createMap(); + return { setModuleResolutionHost, startRecordingFilesWithChangedResolutions, @@ -55,6 +62,11 @@ namespace ts { } function clear() { + failedLookupLocationsWatches.forEach((failedLookupLocationWatcher, failedLookupLocationPath: Path) => { + log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`); + failedLookupLocationWatcher.fileWatcher.close(); + }); + failedLookupLocationsWatches.clear(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -103,8 +115,9 @@ namespace ts { } else { resolution = loader(name, containingFile, compilerOptions, host); - newResolutions.set(name, resolution); + updateFailedLookupLocationWatches(containingFile, name, existingResolution && existingResolution.failedLookupLocations, resolution.failedLookupLocations); } + newResolutions.set(name, resolution); if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { filesWithChangedSetOfUnresolvedImports.push(path); // reset log changes to avoid recording the same file multiple times @@ -155,7 +168,6 @@ namespace ts { } } - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false); @@ -166,6 +178,75 @@ namespace ts { m => m.resolvedModule, r => r.resolvedFileName, logChanges); } + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); + if (failedLookupLocationWatcher) { + failedLookupLocationWatcher.refCount++; + log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`); + } + else { + const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, (__fileName, eventKind) => { + log(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); + // There is some kind of change in the failed lookup location, update the program + clearProgramAndScheduleUpdate(); + }); + failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 }); + } + } + + function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); + Debug.assert(!!failedLookupLocationWatcher); + failedLookupLocationWatcher.refCount--; + if (failedLookupLocationWatcher.refCount) { + log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}: refCount: ${failedLookupLocationWatcher.refCount}`); + } + else { + log(`Watcher: FailedLookupLocations: Status: Closing the file watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); + failedLookupLocationWatcher.fileWatcher.close(); + failedLookupLocationsWatches.delete(failedLookupLocationPath); + } + } + + type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void; + function withFailedLookupLocations(failedLookupLocations: string[], containingFile: string, name: string, fn: FailedLookupLocationAction) { + forEach(failedLookupLocations, failedLookupLocation => { + fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name); + }); + } + + function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: string[], failedLookupLocations: string[]) { + if (failedLookupLocations) { + if (existingFailedLookupLocations) { + const existingWatches = arrayToMap(existingFailedLookupLocations, failedLookupLocation => toPath(failedLookupLocation)); + for (const failedLookupLocation of failedLookupLocations) { + const failedLookupLocationPath = toPath(failedLookupLocation); + if (existingWatches && existingWatches.has(failedLookupLocationPath)) { + // still has same failed lookup location, keep the was + existingWatches.delete(failedLookupLocationPath); + } + else { + // Create new watch + watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name); + } + } + + // Close all the watches that are still present in the existingWatches since those are not the locations looked up for buy new resolution + existingWatches.forEach((failedLookupLocation, failedLookupLocationPath: Path) => + closeFailedLookupLocationWatcher(failedLookupLocation, failedLookupLocationPath, containingFile, name) + ); + } + else { + // Watch all the failed lookup locations + withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation); + } + } + else { + // Close existing watches for the failed locations + withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher); + } + } + function invalidateResolutionCacheOfDeletedFile( deletedFilePath: Path, cache: Map>, @@ -174,6 +255,9 @@ namespace ts { cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); + value.forEach((resolution, name) => { + withFailedLookupLocations(resolution.failedLookupLocations, path, name, closeFailedLookupLocationWatcher); + }); } else if (value) { value.forEach((resolution) => { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 8bf5c542456..6b16474e268 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -271,7 +271,13 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); // Cache for the module resolution - const resolutionCache = createResolutionCache(fileName => toPath(fileName), () => compilerOptions); + const resolutionCache = createResolutionCache( + fileName => toPath(fileName), + () => compilerOptions, + () => clearExistingProgramAndScheduleProgramUpdate(), + (fileName, callback) => system.watchFile(fileName, callback), + s => writeLog(s) + ); // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); @@ -465,6 +471,11 @@ namespace ts { scheduleProgramUpdate(); } + function clearExistingProgramAndScheduleProgramUpdate() { + program = undefined; + scheduleProgramUpdate(); + } + function updateProgram() { timerToUpdateProgram = undefined; reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); diff --git a/src/server/project.ts b/src/server/project.ts index ebad00e03be..c2fab978931 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -217,6 +217,9 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, + () => this.markAsDirty(), + (fileName, callback) => host.watchFile(fileName, callback), + s => this.projectService.logger.info(s), (primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host, this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName()) ); @@ -359,6 +362,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.program = undefined; this.builder = undefined; + this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; this.projectErrors = undefined; From 8dc62484ec32820e749aaa918af863397459bc7d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 5 Aug 2017 02:27:27 -0700 Subject: [PATCH 049/144] Partial implementation for invalidating the program (instead of source file as that would involve more work) so the files are picked up --- src/compiler/resolutionCache.ts | 36 ++++++++++--- src/compiler/watchedProgram.ts | 50 +++++++++++++++---- .../unittests/tsserverProjectSystem.ts | 3 +- src/server/editorServices.ts | 3 +- src/server/project.ts | 20 +++++++- 5 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index f2d20ab19a2..6ca318f7183 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -9,6 +9,7 @@ namespace ts { resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfDeletedFile(filePath: Path): void; + invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void; clear(): void; } @@ -33,8 +34,7 @@ namespace ts { export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - clearProgramAndScheduleUpdate: () => void, - watchForFailedLookupLocation: (fileName: string, callback: FileWatcherCallback) => FileWatcher, + watchForFailedLookupLocation: (failedLookupLocation: string, containingFile: string, name: string) => FileWatcher, log: (s: string) => void, resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache { @@ -54,6 +54,7 @@ namespace ts { resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfDeletedFile, + invalidateResolutionOfChangedFailedLookupLocation, clear }; @@ -185,11 +186,8 @@ namespace ts { log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`); } else { - const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, (__fileName, eventKind) => { - log(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - // There is some kind of change in the failed lookup location, update the program - clearProgramAndScheduleUpdate(); - }); + log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); + const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, containingFile, name); failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 }); } } @@ -260,7 +258,7 @@ namespace ts { }); } else if (value) { - value.forEach((resolution) => { + value.forEach((resolution, __name) => { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { @@ -274,9 +272,31 @@ namespace ts { }); } + function invalidateResolutionCacheOfChangedFailedLookupLocation( + failedLookupLocation: string, + cache: Map>) { + cache.forEach((value, _containingFilePath) => { + if (value) { + value.forEach((resolution, __name) => { + if (resolution && !resolution.isInvalidated && contains(resolution.failedLookupLocations, failedLookupLocation)) { + // TODO: mark the file as needing re-evaluation of module resolution instead of using it blindly. + // Note: Right now this invalidation path is not used at all as it doesnt matter as we are anyways clearing the program, + // which means all the resolutions will be discarded. + resolution.isInvalidated = true; + } + }); + } + }); + } + function invalidateResolutionOfDeletedFile(filePath: Path) { invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } + + function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string) { + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedModuleNames); + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedTypeReferenceDirectives); + } } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 6b16474e268..c668d4a1510 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -255,6 +255,7 @@ namespace ts { let timerToUpdateProgram: any; // timer callback to recompile the program const sourceFilesCache = createMap(); // Cache that stores the source file and version info + let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; @@ -274,8 +275,7 @@ namespace ts { const resolutionCache = createResolutionCache( fileName => toPath(fileName), () => compilerOptions, - () => clearExistingProgramAndScheduleProgramUpdate(), - (fileName, callback) => system.watchFile(fileName, callback), + watchFailedLookupLocation, s => writeLog(s) ); @@ -310,6 +310,19 @@ namespace ts { // Update watches missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); + if (missingFilePathsRequestedForRelease) { + // These are the paths that program creater told us as not in use any more but were missing on the disk. + // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, + // if there is already watcher for it (for missing files) + // At that point our watches were updated, hence now we know that these paths are not tracked and need to be removed + // so that at later time we have correct result of their presence + for (const missingFilePath of missingFilePathsRequestedForRelease) { + if (!missingFilesMap.has(missingFilePath)) { + sourceFilesCache.delete(missingFilePath); + } + } + missingFilePathsRequestedForRelease = undefined; + } afterCompile(host, program, builder); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); @@ -446,8 +459,14 @@ namespace ts { // remove the cached entry. // Note we arent deleting entry if file became missing in new program or // there was version update and new source file was created. - if (hostSourceFileInfo && !isString(hostSourceFileInfo) && hostSourceFileInfo.sourceFile === oldSourceFile) { - sourceFilesCache.delete(oldSourceFile.path); + if (hostSourceFileInfo) { + // record the missing file paths so they can be removed later if watchers arent tracking them + if (isString(hostSourceFileInfo)) { + (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path); + } + else if (hostSourceFileInfo.sourceFile === oldSourceFile) { + sourceFilesCache.delete(oldSourceFile.path); + } } } @@ -471,11 +490,6 @@ namespace ts { scheduleProgramUpdate(); } - function clearExistingProgramAndScheduleProgramUpdate() { - program = undefined; - scheduleProgramUpdate(); - } - function updateProgram() { timerToUpdateProgram = undefined; reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); @@ -547,6 +561,24 @@ namespace ts { } } + function watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) { + return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, containingFile, name)); + } + + function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, containingFile: string, name: string) { + writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); + const path = toPath(failedLookupLocation); + updateCachedSystem(failedLookupLocation, path); + + // TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file + // refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions. + // For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again. + + // resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + program = undefined; + scheduleProgramUpdate(); + } + function watchMissingFilePath(missingFilePath: Path) { return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index b7c71f28191..39776e379c9 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -335,7 +335,8 @@ namespace ts.projectSystem { checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); - checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + const moduleLookupLocations = ["/a/b/c/module.ts", "/a/b/c/module.tsx"]; + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path, ...moduleLookupLocations)); }); it("can handle tsconfig file name with difference casing", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d617bdb2b9e..bf255c7defa 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -247,7 +247,8 @@ namespace ts.server { WildCardDirectories = "Wild card directory", TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", - ConfigFileForInferredRoot = "Config file for the inferred project root" + ConfigFileForInferredRoot = "Config file for the inferred project root", + FailedLookupLocation = "Failed lookup locations in module resolution" } /* @internal */ diff --git a/src/server/project.ts b/src/server/project.ts index c2fab978931..e600c3796e6 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -217,8 +217,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - () => this.markAsDirty(), - (fileName, callback) => host.watchFile(fileName, callback), + (failedLookupLocation, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, containingFile, name), s => this.projectService.logger.info(s), (primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host, this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName()) @@ -235,6 +234,23 @@ namespace ts.server { this.markAsDirty(); } + private watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) { + // There is some kind of change in the failed lookup location, update the program + return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (__fileName, eventKind) => { + this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); + if (this.projectKind === ProjectKind.Configured) { + (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(failedLookupLocation)); + } + this.updateTypes(); + // TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file + // refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions. + // For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again. + // this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + // this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + }); + } + private setInternalCompilerOptionsForEmittingJsFiles() { if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) { this.compilerOptions.noEmitForJsFiles = true; From 7474ba762cadf7aa20d9ffa6fccc3420ad79942c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Sat, 5 Aug 2017 05:01:33 -0700 Subject: [PATCH 050/144] Implementation for invalidating source file containing possibly changed module resolution --- src/compiler/builder.ts | 18 ++++++++---------- src/compiler/core.ts | 2 +- src/compiler/moduleNameResolver.ts | 2 ++ src/compiler/program.ts | 19 +++++++++++++------ src/compiler/resolutionCache.ts | 24 +++++++++++++++++++----- src/compiler/types.ts | 1 + src/compiler/watchedProgram.ts | 17 +++++++---------- src/server/lsHost.ts | 4 ++++ src/server/project.ts | 11 ++++------- src/services/services.ts | 8 ++++++-- src/services/types.ts | 1 + 11 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 489111f26db..c0af4f5b13d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -22,7 +22,7 @@ namespace ts { /** * This is the callback when file infos in the builder are updated */ - onProgramUpdateGraph(program: Program): void; + onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution): void; getFilesAffectedBy(program: Program, path: Path): string[]; emitFile(program: Program, path: Path): EmitOutput; emitChangedFiles(program: Program): EmitOutputDetailed[]; @@ -84,7 +84,7 @@ namespace ts { clear }; - function createProgramGraph(program: Program) { + function createProgramGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) { const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None; if (isModuleEmit !== currentIsModuleEmit) { isModuleEmit = currentIsModuleEmit; @@ -100,7 +100,7 @@ namespace ts { // Remove existing file info removeExistingFileInfo, // We will update in place instead of deleting existing value and adding new one - (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) + (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) ); } @@ -115,8 +115,8 @@ namespace ts { emitHandler.removeScriptInfo(path); } - function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { - if (existingInfo.version !== sourceFile.version) { + function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { + if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) { changedFilesSinceLastEmit.set(sourceFile.path, true); existingInfo.version = sourceFile.version; emitHandler.updateScriptInfo(program, sourceFile); @@ -125,13 +125,13 @@ namespace ts { function ensureProgramGraph(program: Program) { if (!emitHandler) { - createProgramGraph(program); + createProgramGraph(program, noop); } } - function onProgramUpdateGraph(program: Program) { + function onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) { if (emitHandler) { - createProgramGraph(program); + createProgramGraph(program, hasInvalidatedResolution); } } @@ -298,8 +298,6 @@ namespace ts { return result; } - function noop() { } - function getNonModuleEmitHandler(): EmitHandler { return { addScriptInfo: noop, diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 2a41427f2fd..813d805adea 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1220,7 +1220,7 @@ namespace ts { } /** Does nothing. */ - export function noop(): void {} + export function noop(): any {} /** Throws an error because a function is not implemented. */ export function notImplemented(): never { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 027d7ca2025..3258bba6656 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -768,6 +768,8 @@ namespace ts { return !host.directoryExists || host.directoryExists(directoryName); } + export type HasInvalidatedResolution = (sourceFile: Path) => boolean; + /** * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8ce51ac5e8c..58931379d7c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -394,7 +394,7 @@ namespace ts { } export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, - getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean): boolean { + getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean { // If we haven't create a program yet, then it is not up-to-date if (!program) { return false; @@ -432,10 +432,9 @@ namespace ts { return true; function sourceFileUpToDate(sourceFile: SourceFile): boolean { - if (!sourceFile) { - return false; - } - return sourceFile.version === getSourceVersion(sourceFile.path); + return sourceFile && + sourceFile.version === getSourceVersion(sourceFile.path) && + !hasInvalidatedResolution(sourceFile.path); } } @@ -565,6 +564,7 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; + const hasInvalidatedResolution = host.hasInvalidatedResolution || noop; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. @@ -803,7 +803,7 @@ namespace ts { trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile); } } - else { + else if (!hasInvalidatedResolution(oldProgramState.file.path)) { resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState); } @@ -962,6 +962,13 @@ namespace ts { // tentatively approve the file modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } + else if (hasInvalidatedResolution(oldSourceFile.path)) { + // 'module/types' references could have changed + oldProgram.structureIsReused = StructureIsReused.SafeModules; + + // add file to the modified list so that we will resolve it later + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); + } // if file has passed all checks it should be safe to reuse it newSourceFiles.push(newSourceFile); diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6ca318f7183..b6f3e7547f1 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -4,12 +4,18 @@ namespace ts { export interface ResolutionCache { setModuleResolutionHost(host: ModuleResolutionHost): void; + startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; + resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + invalidateResolutionOfDeletedFile(filePath: Path): void; invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void; + + createHasInvalidatedResolution(): HasInvalidatedResolution; + clear(): void; } @@ -40,6 +46,7 @@ namespace ts { let host: ModuleResolutionHost; let filesWithChangedSetOfUnresolvedImports: Path[]; + let filesWithInvalidatedResolutions: Map; const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); @@ -55,6 +62,7 @@ namespace ts { resolveTypeReferenceDirectives, invalidateResolutionOfDeletedFile, invalidateResolutionOfChangedFailedLookupLocation, + createHasInvalidatedResolution, clear }; @@ -82,6 +90,12 @@ namespace ts { return collected; } + function createHasInvalidatedResolution(): HasInvalidatedResolution { + const collected = filesWithInvalidatedResolutions; + filesWithInvalidatedResolutions = undefined; + return path => collected && collected.has(path); + } + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); // return result immediately only if it is .ts, .tsx or .d.ts @@ -250,7 +264,7 @@ namespace ts { cache: Map>, getResult: (s: T) => R, getResultFileName: (result: R) => string | undefined) { - cache.forEach((value, path) => { + cache.forEach((value, path: Path) => { if (path === deletedFilePath) { cache.delete(path); value.forEach((resolution, name) => { @@ -264,6 +278,7 @@ namespace ts { if (result) { if (getResultFileName(result) === deletedFilePath) { resolution.isInvalidated = true; + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); } } } @@ -275,14 +290,13 @@ namespace ts { function invalidateResolutionCacheOfChangedFailedLookupLocation( failedLookupLocation: string, cache: Map>) { - cache.forEach((value, _containingFilePath) => { + cache.forEach((value, containingFile: Path) => { if (value) { value.forEach((resolution, __name) => { if (resolution && !resolution.isInvalidated && contains(resolution.failedLookupLocations, failedLookupLocation)) { - // TODO: mark the file as needing re-evaluation of module resolution instead of using it blindly. - // Note: Right now this invalidation path is not used at all as it doesnt matter as we are anyways clearing the program, - // which means all the resolutions will be discarded. + // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); } }); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 45f5a16a80c..c1ab964af30 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3979,6 +3979,7 @@ namespace ts { resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string; onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; + hasInvalidatedResolution?: HasInvalidatedResolution; } /* @internal */ diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index c668d4a1510..8628b6d049b 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -256,6 +256,7 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files + let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; @@ -292,7 +293,8 @@ namespace ts { function synchronizeProgram() { writeLog(`Synchronizing program`); - if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists)) { + hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) { return; } @@ -306,7 +308,7 @@ namespace ts { // Compile the program program = createProgram(rootFileNames, compilerOptions, compilerHost, program); - builder.onProgramUpdateGraph(program); + builder.onProgramUpdateGraph(program, hasInvalidatedResolution); // Update watches missingFilesMap = updateMissingFilePathsWatch(program, missingFilesMap, watchMissingFilePath, closeMissingFilePathWatcher); @@ -351,7 +353,8 @@ namespace ts { realpath, resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false), - onReleaseOldSourceFile + onReleaseOldSourceFile, + hasInvalidatedResolution }; } @@ -569,13 +572,7 @@ namespace ts { writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); const path = toPath(failedLookupLocation); updateCachedSystem(failedLookupLocation, path); - - // TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file - // refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions. - // For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again. - - // resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); - program = undefined; + resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); scheduleProgramUpdate(); } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 274c3c3aef6..f4cbd402604 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -110,6 +110,10 @@ namespace ts.server { readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; + + /*@internal*/ + hasInvalidatedResolution: HasInvalidatedResolution; + /** * This is the host that is associated with the project. This is normally same as projectService's host * except in Configured projects where it is CachedServerHost so that we can cache the results of the diff --git a/src/server/project.ts b/src/server/project.ts index e600c3796e6..5ea54aca54e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -241,12 +241,8 @@ namespace ts.server { if (this.projectKind === ProjectKind.Configured) { (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(failedLookupLocation)); } - this.updateTypes(); - // TODO: We need more intensive approach wherein we are able to comunicate to the program structure reuser that the even though the source file - // refering to this failed location hasnt changed, it needs to re-evaluate the module resolutions for the invalidated resolutions. - // For now just clear existing program, that should still reuse the source files but atleast compute the resolutions again. - // this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); - // this.markAsDirty(); + this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); }); } @@ -605,6 +601,7 @@ namespace ts.server { */ updateGraph(): boolean { this.resolutionCache.startRecordingFilesWithChangedResolutions(); + this.lsHost.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); let hasChanges = this.updateGraphWorker(); @@ -640,7 +637,7 @@ namespace ts.server { // otherwise tell it to drop its internal state if (this.builder) { if (this.languageServiceEnabled && this.compileOnSaveEnabled) { - this.builder.onProgramUpdateGraph(this.program); + this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution); } else { this.builder.clear(); diff --git a/src/services/services.ts b/src/services/services.ts index 40a446e0967..14b5ddd85c4 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1115,8 +1115,11 @@ namespace ts { // Get a fresh cache of the host information let hostCache = new HostCache(host, getCanonicalFileName); const rootFileNames = hostCache.getRootFileNames(); + + const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || noop; + // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists)) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution)) { return; } @@ -1155,7 +1158,8 @@ namespace ts { getDirectories: path => { return host.getDirectories ? host.getDirectories(path) : []; }, - onReleaseOldSourceFile + onReleaseOldSourceFile, + hasInvalidatedResolution }; if (host.trace) { compilerHost.trace = message => host.trace(message); diff --git a/src/services/types.ts b/src/services/types.ts index e17df08378a..018e8941765 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -185,6 +185,7 @@ namespace ts { */ resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + hasInvalidatedResolution?: HasInvalidatedResolution; directoryExists?(directoryName: string): boolean; /* From 6385f7e3bb2c97118211754b2a11ff2356fe11ab Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 051/144] Get semantic diagnostics for the program from builder so that it caches the errors of unchanged files --- src/compiler/builder.ts | 57 ++++++++++- src/compiler/resolutionCache.ts | 2 +- src/compiler/tsc.ts | 25 ++++- src/compiler/watchedProgram.ts | 174 +++++++++++++++----------------- 4 files changed, 160 insertions(+), 98 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c0af4f5b13d..7d4b1ce37a2 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -26,6 +26,7 @@ namespace ts { getFilesAffectedBy(program: Program, path: Path): string[]; emitFile(program: Program, path: Path): EmitOutput; emitChangedFiles(program: Program): EmitOutputDetailed[]; + getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[]; clear(): void; } @@ -74,6 +75,7 @@ namespace ts { // Last checked shape signature for the file info type FileInfo = { version: string; signature: string; }; let fileInfos: Map; + const semanticDiagnosticsPerFile = createMap(); let changedFilesSinceLastEmit: Map; let emitHandler: EmitHandler; return { @@ -81,6 +83,7 @@ namespace ts { getFilesAffectedBy, emitFile, emitChangedFiles, + getSemanticDiagnostics, clear }; @@ -90,6 +93,7 @@ namespace ts { isModuleEmit = currentIsModuleEmit; emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); fileInfos = undefined; + semanticDiagnosticsPerFile.clear(); } changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap(); @@ -104,20 +108,27 @@ namespace ts { ); } + function registerChangedFile(path: Path) { + changedFilesSinceLastEmit.set(path, true); + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(path); + } + function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { - changedFilesSinceLastEmit.set(sourceFile.path, true); + registerChangedFile(sourceFile.path); emitHandler.addScriptInfo(program, sourceFile); return { version: sourceFile.version, signature: undefined }; } function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) { - changedFilesSinceLastEmit.set(path, true); + registerChangedFile(path); emitHandler.removeScriptInfo(path); } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) { - changedFilesSinceLastEmit.set(sourceFile.path, true); + registerChangedFile(sourceFile.path); + semanticDiagnosticsPerFile.delete(sourceFile.path); existingInfo.version = sourceFile.version; emitHandler.updateScriptInfo(program, sourceFile); } @@ -170,6 +181,9 @@ namespace ts { const sourceFile = program.getSourceFile(file); seenFiles.set(file, sourceFile); if (sourceFile) { + // Any affected file shouldnt have the cached diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed; result.push(emitOutput); @@ -189,10 +203,45 @@ namespace ts { return result; } + function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[] { + ensureProgramGraph(program); + + // Ensure that changed files have cleared their respective + if (changedFilesSinceLastEmit) { + changedFilesSinceLastEmit.forEach((__value, path: Path) => { + const affectedFiles = getFilesAffectedBy(program, path); + for (const file of affectedFiles) { + const sourceFile = program.getSourceFile(file); + if (sourceFile) { + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + } + }); + } + + let diagnostics: Diagnostic[]; + for (const sourceFile of program.getSourceFiles()) { + const path = sourceFile.path; + const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); + // Report the semantic diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + diagnostics = concatenate(diagnostics, cachedDiagnostics); + } + else { + // Diagnostics werent cached, get them from program, and cache the result + const cachedDiagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); + semanticDiagnosticsPerFile.set(path, cachedDiagnostics); + diagnostics = concatenate(diagnostics, cachedDiagnostics); + } + } + return diagnostics || emptyArray; + } + function clear() { isModuleEmit = undefined; emitHandler = undefined; fileInfos = undefined; + semanticDiagnosticsPerFile.clear(); } /** @@ -356,7 +405,7 @@ namespace ts { const referencedByPaths = referencedBy.get(path); if (referencedByPaths) { for (const path of referencedByPaths) { - changedFilesSinceLastEmit.set(path, true); + registerChangedFile(path); } referencedBy.delete(path); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b6f3e7547f1..615340e4872 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -276,7 +276,7 @@ namespace ts { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { - if (getResultFileName(result) === deletedFilePath) { + if (toPath(getResultFileName(result)) === deletedFilePath) { resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index b89a41a2d23..78586e5479a 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -157,7 +157,7 @@ namespace ts { enableStatistics(compilerOptions); const program = createProgram(rootFileNames, compilerOptions, compilerHost); - const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); + const exitStatus = compileProgram(program); reportStatistics(program); return sys.exit(exitStatus); @@ -174,6 +174,29 @@ namespace ts { return watchingHost; } + function compileProgram(program: Program): ExitStatus { + let diagnostics: Diagnostic[]; + + // First get and report any syntactic errors. + diagnostics = program.getSyntacticDiagnostics(); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + diagnostics = program.getSemanticDiagnostics(); + } + } + + // Emit and report any errors we ran into. + const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); + diagnostics = diagnostics.concat(emitDiagnostics); + + return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); + } + function enableStatistics(compilerOptions: CompilerOptions) { if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 8628b6d049b..d4301ea839d 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -101,29 +101,12 @@ namespace ts { } } - export function compileProgram(system: System, program: Program, emitProgram: () => EmitResult, - reportDiagnostic: DiagnosticReporter): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics(); - } - } - - // Emit and report any errors we ran into. - const emitOutput = emitProgram(); - diagnostics = diagnostics.concat(emitOutput.diagnostics); - + export function handleEmitOutputAndReportErrors(system: System, program: Program, + emittedFiles: string[], emitSkipped: boolean, + diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter + ): ExitStatus { reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic); - reportEmittedFiles(emitOutput.emittedFiles, system); + reportEmittedFiles(emittedFiles, system); if (program.getCompilerOptions().listFiles) { forEach(program.getSourceFiles(), file => { @@ -131,7 +114,7 @@ namespace ts { }); } - if (emitOutput.emitSkipped && diagnostics.length > 0) { + if (emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; } @@ -143,74 +126,6 @@ namespace ts { return ExitStatus.Success; } - function emitWatchedProgram(host: System, program: Program, builder: Builder) { - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - - const result = builder.emitChangedFiles(program); - switch (result.length) { - case 0: - emitSkipped = true; - break; - case 1: - const emitOutput = result[0]; - ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); - writeOutputFiles(emitOutput.outputFiles); - break; - default: - for (const emitOutput of result) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - diagnostics = concatenate(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - writeOutputFiles(emitOutput.outputFiles); - } - } - - return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - host.writeFile(fileName, data, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); - } - } - - function writeOutputFiles(outputFiles: OutputFile[]) { - if (outputFiles) { - for (const outputFile of outputFiles) { - const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); - if (error) { - diagnostics.push(error); - } - if (emittedFiles) { - emittedFiles.push(outputFile.name); - } - } - } - } - } - export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys, parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter, reportWatchDiagnostic?: DiagnosticReporter @@ -228,7 +143,82 @@ namespace ts { }; function compileWatchedProgram(host: System, program: Program, builder: Builder) { - return compileProgram(system, program, () => emitWatchedProgram(host, program, builder), reportDiagnostic); + // First get and report any syntactic errors. + let diagnostics = program.getSyntacticDiagnostics(); + let reportSemanticDiagnostics = false; + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + reportSemanticDiagnostics = true; + } + } + + // Emit and report any errors we ran into. + const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; + let sourceMaps: SourceMapData[]; + let emitSkipped: boolean; + + const result = builder.emitChangedFiles(program); + if (result.length === 0) { + emitSkipped = true; + } + else { + for (const emitOutput of result) { + if (emitOutput.emitSkipped) { + emitSkipped = true; + } + diagnostics = concatenate(diagnostics, emitOutput.diagnostics); + sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); + writeOutputFiles(emitOutput.outputFiles); + } + } + + if (reportSemanticDiagnostics) { + diagnostics = diagnostics.concat(builder.getSemanticDiagnostics(program)); + } + return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, + diagnostics, reportDiagnostic); + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, data, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + return createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, e); + } + } + + function writeOutputFiles(outputFiles: OutputFile[]) { + if (outputFiles) { + for (const outputFile of outputFiles) { + const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark); + if (error) { + diagnostics.push(error); + } + if (emittedFiles) { + emittedFiles.push(outputFile.name); + } + } + } + } } } From 65521bc25949a724fc5e53d43ea8dc6d23a5966d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 052/144] Feedback from the PR --- src/compiler/commandLineParser.ts | 6 +- src/compiler/types.ts | 8 +- src/harness/unittests/projectErrors.ts | 18 ++-- .../unittests/tsserverProjectSystem.ts | 84 ++++++++++++++----- src/server/editorServices.ts | 9 +- src/server/project.ts | 10 +++ 6 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index efb574ac971..21cf4275cca 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1478,7 +1478,11 @@ namespace ts { } } - export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName?: string) { + export function isErrorNoInputFiles(error: Diagnostic) { + return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; + } + + export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { return createCompilerDiagnostic( Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, configFileName || "tsconfig.json", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8929b19bfe3..ed0a8128458 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3930,9 +3930,9 @@ namespace ts { } export interface ResolvedModuleWithFailedLookupLocations { - resolvedModule: ResolvedModuleFull | undefined; + readonly resolvedModule: ResolvedModuleFull | undefined; /* @internal */ - failedLookupLocations: string[]; + readonly failedLookupLocations: string[]; /*@internal*/ isInvalidated?: boolean; } @@ -3945,8 +3945,8 @@ namespace ts { } export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { - resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; - failedLookupLocations: string[]; + readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; + readonly failedLookupLocations: string[]; /*@internal*/ isInvalidated?: boolean; } diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 0143aee7701..4d0757e3c2f 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -23,11 +23,9 @@ namespace ts.projectSystem { function checkDiagnosticsWithLinePos(errors: server.protocol.DiagnosticWithLinePosition[], expectedErrors: string[]) { assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); if (expectedErrors.length) { - for (let i = 0; i < errors.length; i++) { - const actualMessage = errors[i].message; - const expectedMessage = expectedErrors[i]; - assert.isTrue(actualMessage.indexOf(errors[i].message) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); - } + zipWith(errors, expectedErrors, ({ message: actualMessage }, expectedMessage) => { + assert.isTrue(startsWith(actualMessage, actualMessage), `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`); + }); } } @@ -40,12 +38,11 @@ namespace ts.projectSystem { path: "/a/b/applib.ts", content: "" }; - // only file1 exists - expect error const host = createServerHost([file1, libFile]); const session = createSession(host); const projectService = session.getProjectService(); const projectFileName = "/a/b/test.csproj"; - const compilerOptionsRequest = { + const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { type: "request", command: server.CommandNames.CompilerOptionsDiagnosticsFull, seq: 2, @@ -61,19 +58,20 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { externalProjects: 1 }); const diags = session.executeCommand(compilerOptionsRequest).response; + // only file1 exists - expect error checkDiagnosticsWithLinePos(diags, ["File '/a/b/applib.ts' not found."]); } - // only file2 exists - expect error host.reloadFS([file2, libFile]); { + // only file2 exists - expect error checkNumberOfProjects(projectService, { externalProjects: 1 }); const diags = session.executeCommand(compilerOptionsRequest).response; checkDiagnosticsWithLinePos(diags, ["File '/a/b/app.ts' not found."]); } - // both files exist - expect no errors host.reloadFS([file1, file2, libFile]); { + // both files exist - expect no errors checkNumberOfProjects(projectService, { externalProjects: 1 }); const diags = session.executeCommand(compilerOptionsRequest).response; checkDiagnosticsWithLinePos(diags, []); @@ -99,7 +97,7 @@ namespace ts.projectSystem { openFilesForSession([file1], session); checkNumberOfProjects(projectService, { configuredProjects: 1 }); const project = configuredProjectAt(projectService, 0); - const compilerOptionsRequest = { + const compilerOptionsRequest: server.protocol.CompilerOptionsDiagnosticsRequest = { type: "request", command: server.CommandNames.CompilerOptionsDiagnosticsFull, seq: 2, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fa6ffc74e65..17953a8bcd5 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -253,29 +253,19 @@ namespace ts.projectSystem { entries: FSEntry[]; } - export function isFolder(s: FSEntry): s is Folder { + export function isFolder(s: FSEntry | undefined): s is Folder { return s && isArray((s).entries); } - export function isFile(s: FSEntry): s is File { + export function isFile(s: FSEntry | undefined): s is File { return s && typeof (s).content === "string"; } - function invokeDirectoryWatcher(callbacks: DirectoryWatcherCallback[], getRelativeFilePath: () => string) { + function invokeWatcherCallbacks(callbacks: T[], invokeCallback: (cb: T) => void): void { if (callbacks) { const cbs = callbacks.slice(); for (const cb of cbs) { - const fileName = getRelativeFilePath(); - cb(fileName); - } - } - } - - function invokeFileWatcher(callbacks: FileWatcherCallback[], fileName: string, eventId: FileWatcherEventKind) { - if (callbacks) { - const cbs = callbacks.slice(); - for (const cb of cbs) { - cb(fileName, eventId); + invokeCallback(cb); } } } @@ -380,7 +370,7 @@ namespace ts.projectSystem { private readonly output: string[] = []; - private fs: Map = createMap(); + private fs = createMap(); private getCanonicalFileName: (s: string) => string; private toPath: (f: string) => Path; private timeoutCallbacks = new Callbacks(); @@ -510,8 +500,12 @@ namespace ts.projectSystem { } else { Debug.assert(fileOrFolder.entries.length === 0); - invokeDirectoryWatcher(this.watchedDirectories.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(fileOrFolder.path), () => this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath)); + const relativePath = this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath); + // Invoke directory and recursive directory watcher for the folder + // Here we arent invoking recursive directory watchers for the base folders + // since that is something we would want to do for both file as well as folder we are deleting + invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => cb(relativePath)); } if (basePath !== fileOrFolder.path) { @@ -524,22 +518,31 @@ namespace ts.projectSystem { } } - private invokeFileWatcher(fileFullPath: string, eventId: FileWatcherEventKind) { + private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) { const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - invokeFileWatcher(callbacks, getBaseFileName(fileFullPath), eventId); + const fileName = getBaseFileName(fileFullPath); + invokeWatcherCallbacks(callbacks, cb => cb(fileName, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); } + /** + * This will call the directory watcher for the foldeFullPath and recursive directory watchers for this and base folders + */ private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectories.get(this.toPath(folderFullPath)), () => this.getRelativePathToDirectory(folderFullPath, fileName)); + const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName); + invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => cb(relativePath)); this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); } + /** + * This will call the recursive directory watcher for this directory as well as all the base directories + */ private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { - invokeDirectoryWatcher(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), () => this.getRelativePathToDirectory(fullPath, fileName)); + const relativePath = this.getRelativePathToDirectory(fullPath, fileName); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => cb(relativePath)); const basePath = getDirectoryPath(fullPath); if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { this.invokeRecursiveDirectoryWatcher(basePath, fileName); @@ -885,6 +888,45 @@ namespace ts.projectSystem { checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); }); + it("create configured project with the file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: ` + { + "compilerOptions": {}, + "include": ["*.ts"] + }` + }; + const file1: FileOrFolder = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2: FileOrFolder = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const file3: FileOrFolder = { + path: "/a/b/c/f3.ts", + content: "let z = 1" + }; + + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ false); + }); + it("add and then remove a config file in a folder with loose files", () => { const configFile: FileOrFolder = { path: "/a/b/tsconfig.json", diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 003bb87f6a9..b71bc939097 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -665,14 +665,7 @@ namespace ts.server { const configFileSpecs = project.configFileSpecs; const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); - const errors = project.getAllProjectErrors(); - const isErrorNoInputFiles = (error: Diagnostic) => error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; - if (result.fileNames.length !== 0) { - filterMutate(errors, error => !isErrorNoInputFiles(error)); - } - else if (!configFileSpecs.filesSpecs && !some(errors, isErrorNoInputFiles)) { - errors.push(getErrorForNoInputFiles(configFileSpecs, configFilename)); - } + project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } diff --git a/src/server/project.ts b/src/server/project.ts index 62c450f9f4f..d36551bdb5a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1226,6 +1226,16 @@ namespace ts.server { getEffectiveTypeRoots() { return getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; } + + /*@internal*/ + updateErrorOnNoInputFiles(hasFileNames: boolean) { + if (hasFileNames) { + filterMutate(this.projectErrors, error => !isErrorNoInputFiles(error)); + } + else if (!this.configFileSpecs.filesSpecs && !some(this.projectErrors, isErrorNoInputFiles)) { + this.projectErrors.push(getErrorForNoInputFiles(this.configFileSpecs, this.getConfigFilePath())); + } + } } /** From 27988bf33a7b7c497bbb208c4300cd404ee891b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 053/144] More updates based on PR feedback --- .../unittests/tsserverProjectSystem.ts | 65 ++++++--- src/server/editorServices.ts | 135 ++++++++++-------- src/server/lsHost.ts | 10 +- 3 files changed, 128 insertions(+), 82 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 17953a8bcd5..7d70aa74fdf 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -407,17 +407,20 @@ namespace ts.projectSystem { // Update file if (currentEntry.content !== fileOrFolder.content) { currentEntry.content = fileOrFolder.content; + currentEntry.fileSize = fileOrFolder.fileSize; this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); } } else { // TODO: Changing from file => folder + Debug.fail(`Currently ${path} is file and new FS makes it folder which isnt supported yet`); } } else { // Folder if (typeof fileOrFolder.content === "string") { // TODO: Changing from folder => file + Debug.fail(`Currently ${path} is folder and new FS makes it file which isnt supported yet`); } else { // Folder update: Nothing to do. @@ -778,6 +781,20 @@ namespace ts.projectSystem { } } + type ErrorInformation = { diagnosticMessage: DiagnosticMessage, errorTextArguments?: string[] }; + function getProtocolDiagnosticMessage({ diagnosticMessage, errorTextArguments = [] }: ErrorInformation) { + return formatStringFromArgs(diagnosticMessage.message, errorTextArguments); + } + + function verifyDiagnostics(actual: server.protocol.Diagnostic[], expected: ErrorInformation[]) { + const expectedErrors = expected.map(getProtocolDiagnosticMessage); + assert.deepEqual(actual.map(diag => flattenDiagnosticMessageText(diag.text, "\n")), expectedErrors); + } + + function verifyNoDiagnostics(actual: server.protocol.Diagnostic[]) { + verifyDiagnostics(actual, []); + } + describe("tsserver-project-system", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -1111,10 +1128,13 @@ namespace ts.projectSystem { server.CommandNames.SemanticDiagnosticsSync, { file: file1.path } ); - let diags = session.executeCommand(getErrRequest).response; // Two errors: CommonFile2 not found and cannot find name y - assert.equal(diags.length, 2, diags.map(diag => flattenDiagnosticMessageText(diag.text, "\n")).join("\n")); + let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] }, + { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] } + ]); host.reloadFS([file1, commonFile2, libFile]); host.runQueuedTimeoutCallbacks(); @@ -1122,8 +1142,8 @@ namespace ts.projectSystem { assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); checkProjectRootFiles(project, [file1.path]); checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); - diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + diags = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); }); it("should create new inferred projects for files excluded from a configured project", () => { @@ -3110,15 +3130,18 @@ namespace ts.projectSystem { server.CommandNames.SemanticDiagnosticsSync, { file: file1.path } ); - let diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1]); host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response; + diags = session.executeCommand(getErrRequest).response; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); assert.equal(diags.length, 1); moduleFile.path = moduleFileOldPath; @@ -3133,8 +3156,8 @@ namespace ts.projectSystem { session.executeCommand(changeRequest); host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + diags = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); }); it("should restore the states for configured projects", () => { @@ -3158,22 +3181,24 @@ namespace ts.projectSystem { server.CommandNames.SemanticDiagnosticsSync, { file: file1.path } ); - let diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; host.reloadFS([moduleFile, file1, configFile]); host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 1); + diags = session.executeCommand(getErrRequest).response; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); moduleFile.path = moduleFileOldPath; host.reloadFS([moduleFile, file1, configFile]); host.runQueuedTimeoutCallbacks(); - diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + diags = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); }); it("should property handle missing config files", () => { @@ -3239,8 +3264,10 @@ namespace ts.projectSystem { server.CommandNames.SemanticDiagnosticsSync, { file: file1.path } ); - let diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 1); + let diags: server.protocol.Diagnostic[] = session.executeCommand(getErrRequest).response; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_module_0, errorTextArguments: ["./moduleFile"] } + ]); host.reloadFS([file1, moduleFile]); host.runQueuedTimeoutCallbacks(); @@ -3253,8 +3280,8 @@ namespace ts.projectSystem { session.executeCommand(changeRequest); // Recheck - diags = session.executeCommand(getErrRequest).response; - assert.equal(diags.length, 0); + diags = session.executeCommand(getErrRequest).response; + verifyNoDiagnostics(diags); }); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b71bc939097..2d41a3ccb30 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -283,10 +283,17 @@ namespace ts.server { type ConfigFileExistence = { /** * Cached value of existence of config file + * It is true if there is configured project open for this file. + * It can be either true or false if this is the config file that is being watched by inferred project + * to decide when to update the structure so that it knows about updating the project for its files + * (config file may include the inferred project files after the change and hence may be wont need to be in inferred project) */ exists: boolean; /** - * The value in the open files map is true if the file is inferred project root + * The value in the trackingOpenFilesMap is true if the open file is inferred project root + * and hence tracking changes to this config file + * (either config file is already present but doesnt include the open file in the project structure or config file doesnt exist) + * * Otherwise its false */ trackingOpenFilesMap: Map; @@ -345,7 +352,14 @@ namespace ts.server { private compilerOptionsForInferredProjects: CompilerOptions; private compileOnSaveForInferredProjects: boolean; private readonly projectToSizeMap: Map = createMap(); - private readonly mapOfConfigFilePresence: Map; + /** + * This is a map of config file paths existance that doesnt need query to disk + * - The entry can be present because there is inferred project that needs to watch addition of config file to folder + * In this case the exists could be true/false based on config file is present or not + * - Or it is present if we have configured project open with config file at that location + * In this case the exists property is always true + */ + private readonly mapOfConfigFilePresence = createMap(); private readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; @@ -389,7 +403,6 @@ namespace ts.server { this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); - this.mapOfConfigFilePresence = createMap(); this.throttledOperations = new ThrottledOperations(this.host); this.typingsInstaller.attach(this); @@ -415,7 +428,7 @@ namespace ts.server { } ensureInferredProjectsUpToDate_TestOnly() { - this.ensureInferredProjectsUpToDate(); + this.ensureProjectStructuresUptoDate(); } getCompilerOptionsForInferredProjects() { @@ -505,7 +518,7 @@ namespace ts.server { return undefined; } if (isInferredProjectName(projectName)) { - this.ensureInferredProjectsUpToDate(); + this.ensureProjectStructuresUptoDate(); return findProjectByName(projectName, this.inferredProjects); } return this.findExternalProjectByProjectName(projectName) || this.findConfiguredProjectByProjectName(toNormalizedPath(projectName)); @@ -513,7 +526,7 @@ namespace ts.server { getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean) { if (refreshInferredProjects) { - this.ensureInferredProjectsUpToDate(); + this.ensureProjectStructuresUptoDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); return scriptInfo && scriptInfo.getDefaultProject(); @@ -521,9 +534,16 @@ namespace ts.server { /** * Ensures the project structures are upto date - * @param refreshInferredProjects when true updates the inferred projects even if there is no pending work + * This means, + * - if there are changedFiles (the files were updated but their containing project graph was not upto date), + * their project graph is updated + * - If there are pendingProjectUpdates (scheduled to be updated with delay so they can batch update the graph if there are several changes in short time span) + * their project graph is updated + * - If there were project graph updates and/or there was pending inferred project update and/or called forced the inferred project structure refresh + * Inferred projects are created/updated/deleted based on open files states + * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures */ - private ensureInferredProjectsUpToDate(refreshInferredProjects?: boolean) { + private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?: boolean) { if (this.changedFiles) { let projectsToUpdate: Project[]; if (this.changedFiles.length === 1) { @@ -546,7 +566,7 @@ namespace ts.server { this.updateProjectGraphs(projectsToUpdate); } - if (this.pendingInferredProjectUpdate || refreshInferredProjects) { + if (this.pendingInferredProjectUpdate || forceInferredProjectsRefresh) { this.pendingInferredProjectUpdate = false; this.refreshInferredProjects(); } @@ -584,26 +604,22 @@ namespace ts.server { const info = this.getScriptInfoForNormalizedPath(fileName); if (!info) { this.logger.info(`Error: got watch notification for unknown file: ${fileName}`); - return; } - - if (eventKind === FileWatcherEventKind.Deleted) { + else if (eventKind === FileWatcherEventKind.Deleted) { // File was deleted this.handleDeletedFile(info); } - else { - if (!info.isScriptOpen()) { - if (info.containingProjects.length === 0) { - // Orphan script info, remove it as we can always reload it on next open file request - this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange); - this.filenameToScriptInfo.delete(info.path); - } - else { - // file has been changed which might affect the set of referenced files in projects that include - // this file and set of inferred projects - info.reloadFromFile(); - this.delayUpdateProjectGraphs(info.containingProjects); - } + else if (!info.isScriptOpen()) { + if (info.containingProjects.length === 0) { + // Orphan script info, remove it as we can always reload it on next open file request + this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange); + this.filenameToScriptInfo.delete(info.path); + } + else { + // file has been changed which might affect the set of referenced files in projects that include + // this file and set of inferred projects + info.reloadFromFile(); + this.delayUpdateProjectGraphs(info.containingProjects); } } } @@ -821,7 +837,7 @@ namespace ts.server { this.removeProject(project); } - // collect orphanted files and try to re-add them as newly opened + // collect orphaned files and try to re-add them as newly opened // treat orphaned files as newly opened // for all open files for (const f of this.openFiles) { @@ -866,7 +882,7 @@ namespace ts.server { return configFilePresenceInfo.exists; } - // Theorotically we should be adding watch for the directory here itself. + // Theoretically we should be adding watch for the directory here itself. // In practice there will be very few scenarios where the config file gets added // somewhere inside the another config file directory. // And technically we could handle that case in configFile's directory watcher in some cases @@ -935,25 +951,26 @@ namespace ts.server { } private logConfigFileWatchUpdate(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { - if (this.logger.loggingEnabled()) { - const inferredRoots: string[] = []; - const otherFiles: string[] = []; - configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => { - const info = this.getScriptInfoForPath(key); - if (value) { - inferredRoots.push(info.fileName); - } - else { - otherFiles.push(info.fileName); - } - }); - const watchType = status === ConfigFileWatcherStatus.UpdatedCallback || - status === ConfigFileWatcherStatus.ReloadingFiles || - status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ? - (configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) : - ""; - this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`); + if (!this.logger.loggingEnabled()) { + return; } + const inferredRoots: string[] = []; + const otherFiles: string[] = []; + configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => { + const info = this.getScriptInfoForPath(key); + if (value) { + inferredRoots.push(info.fileName); + } + else { + otherFiles.push(info.fileName); + } + }); + const watchType = status === ConfigFileWatcherStatus.UpdatedCallback || + status === ConfigFileWatcherStatus.ReloadingFiles || + status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ? + (configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) : + ""; + this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`); } private closeConfigFileWatcherIfInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, @@ -998,7 +1015,7 @@ namespace ts.server { */ private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.isScriptOpen()); - this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => this.closeConfigFileWatchForClosedScriptInfo(configFileName, canonicalConfigFilePath, info) ); } @@ -1034,7 +1051,7 @@ namespace ts.server { /* @internal */ startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); - this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => this.watchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info) ); } @@ -1060,7 +1077,7 @@ namespace ts.server { */ /* @internal */ stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { - this.enumerateConfigFileLocations(info, (configFileName, canonicalConfigFilePath) => + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => this.closeWatchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info, reason) ); } @@ -1073,7 +1090,7 @@ namespace ts.server { * The server must start searching from the directory containing * the newly opened file. */ - private enumerateConfigFileLocations(info: ScriptInfo, + private forEachConfigFileLocation(info: ScriptInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void, projectRootPath?: NormalizedPath) { let searchPath = asNormalizedPath(getDirectoryPath(info.fileName)); @@ -1113,8 +1130,8 @@ namespace ts.server { private getConfigFileNameForFile(info: ScriptInfo, projectRootPath?: NormalizedPath) { Debug.assert(info.isScriptOpen()); this.logger.info(`Search path: ${getDirectoryPath(info.fileName)}`); - const configFileName = this.enumerateConfigFileLocations(info, - (configFileName: NormalizedPath, canonicalConfigFilePath: string) => + const configFileName = this.forEachConfigFileLocation(info, + (configFileName, canonicalConfigFilePath) => this.configFileExists(configFileName, canonicalConfigFilePath, info), projectRootPath ); @@ -1684,16 +1701,18 @@ namespace ts.server { } /** - * This function is to update the project structure for every projects. + * This function is to update the project structure for every inferred project. * It is called on the premise that all the configured projects are * up to date. + * This will go through open files and assign them to inferred project if open file is not part of any other project + * After that all the inferred project graphs are updated */ private refreshInferredProjects() { this.logger.info("refreshInferredProjects: updating project structure from ..."); this.printProjects(); for (const info of this.openFiles) { - // collect all orphanted script infos from open files + // collect all orphaned script infos from open files if (info.containingProjects.length === 0) { this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ false); } @@ -1826,7 +1845,7 @@ namespace ts.server { // if files were open or closed then explicitly refresh list of inferred projects // otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update if (openFiles || closedFiles) { - this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); + this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } } @@ -1849,7 +1868,7 @@ namespace ts.server { } this.externalProjectToConfiguredProjectMap.delete(fileName); if (shouldRefreshInferredProjects && !suppressRefresh) { - this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); + this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } } else { @@ -1858,7 +1877,7 @@ namespace ts.server { if (externalProject) { this.removeProject(externalProject); if (!suppressRefresh) { - this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); + this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } } } @@ -1882,7 +1901,7 @@ namespace ts.server { this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true); }); - this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); + this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } /** Makes a filename safe to insert in a RegExp */ @@ -2077,7 +2096,7 @@ namespace ts.server { this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); } if (!suppressRefreshOfInferredProjects) { - this.ensureInferredProjectsUpToDate(/*refreshInferredProjects*/ true); + this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); } } } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index fb1cd80b660..3bba5513227 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -154,19 +154,19 @@ namespace ts.server { const path = this.toPath(fileOrFolder); const existingResult = this.cachedReadDirectoryResult.get(path); if (existingResult) { + // This was a folder already present, remove it if this doesnt exist any more if (!this.host.directoryExists(fileOrFolder)) { this.cachedReadDirectoryResult.delete(path); } } else { - // Was this earlier file + // This was earlier a file (hence not in cached directory contents) + // or we never cached the directory containing it const parentResult = this.cachedReadDirectoryResult.get(getDirectoryPath(path)); if (parentResult) { const baseName = getBaseFileName(fileOrFolder); - if (parentResult) { - parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); - parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); - } + parentResult.files = this.updateFileSystemEntry(parentResult.files, baseName, this.host.fileExists(path)); + parentResult.directories = this.updateFileSystemEntry(parentResult.directories, baseName, this.host.directoryExists(path)); } } } From 02b8a7de659c09756c367fdcab5d2289cd69b982 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 054/144] More work on PR feedback --- src/compiler/types.ts | 6 ++ src/compiler/utilities.ts | 4 + src/server/editorServices.ts | 10 +- src/server/project.ts | 176 ++++++++++++++++++----------------- src/server/utilities.ts | 97 ++++++++----------- 5 files changed, 146 insertions(+), 147 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ed0a8128458..2b680db71bc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3688,7 +3688,13 @@ namespace ts { export interface ConfigFileSpecs { filesSpecs: ReadonlyArray; + /** + * Present to report errors (user specified specs), validatedIncludeSpecs are used for file name matching + */ includeSpecs: ReadonlyArray; + /** + * Present to report errors (user specified specs), validatedExcludeSpecs are used for file name matching + */ excludeSpecs: ReadonlyArray; validatedIncludeSpecs: ReadonlyArray; validatedExcludeSpecs: ReadonlyArray; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 76d242e8b08..14cab78bc84 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3607,6 +3607,10 @@ namespace ts { export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; } + + export function isRecursiveDirectoryWatch(flags: WatchDirectoryFlags) { + return (flags & WatchDirectoryFlags.Recursive) !== 0; + } } namespace ts { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2d41a3ccb30..5ca8a54d71f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -280,7 +280,7 @@ namespace ts.server { /* @internal */ export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; - type ConfigFileExistence = { + interface ConfigFileExistence { /** * Cached value of existence of config file * It is true if there is configured project open for this file. @@ -302,7 +302,7 @@ namespace ts.server { * The watcher is present only when there is no open configured project for this config file */ configFileWatcher?: FileWatcher; - }; + } export interface ProjectServiceOptions { host: ServerHost; @@ -1606,13 +1606,15 @@ namespace ts.server { } /* @internal */ - closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, recursive: boolean, reason: WatcherCloseReason) { + closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, flags: WatchDirectoryFlags, reason: WatcherCloseReason) { + const recursive = isRecursiveDirectoryWatch(flags); 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) { + addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, flags: WatchDirectoryFlags) { + const recursive = isRecursiveDirectoryWatch(flags); 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)); diff --git a/src/server/project.ts b/src/server/project.ts index d36551bdb5a..06e4205bc8f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -327,10 +327,12 @@ namespace ts.server { this.lsHost = undefined; // Clean up file watchers waiting for missing files - cleanExistingMap(this.missingFilesMap, (missingFilePath, fileWatcher) => { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose); - }); - this.missingFilesMap = undefined; + if (this.missingFilesMap) { + clearMap(this.missingFilesMap, (missingFilePath, fileWatcher) => { + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose); + }); + this.missingFilesMap = undefined; + } // signal language service to release source files acquired from document registry this.languageService.dispose(); @@ -642,34 +644,37 @@ namespace ts.server { const missingFilePaths = this.program.getMissingFilePaths(); const newMissingFilePathMap = arrayToSet(missingFilePaths); // Update the missing file paths watcher - this.missingFilesMap = mutateExistingMapWithNewSet( - this.missingFilesMap, newMissingFilePathMap, - // Watch the missing files - missingFilePath => { - const fileWatcher = this.projectService.addFileWatcher( - WatchType.MissingFilePath, this, missingFilePath, - (filename, eventKind) => { - if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { - this.missingFilesMap.delete(missingFilePath); - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); + mutateMap( + this.missingFilesMap || (this.missingFilesMap = createMap()), + newMissingFilePathMap, + { + // Watch the missing files + createNewValue: missingFilePath => { + const fileWatcher = this.projectService.addFileWatcher( + WatchType.MissingFilePath, this, missingFilePath, + (filename, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { + this.missingFilesMap.delete(missingFilePath); + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); - if (this.projectKind === ProjectKind.Configured) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + if (this.projectKind === ProjectKind.Configured) { + const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); + (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); + } + + // When a missing file is created, we should update the graph. + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - - // When a missing file is created, we should update the graph. - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - } - ); - return fileWatcher; - }, - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - (missingFilePath, fileWatcher) => { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded); + ); + return fileWatcher; + }, + // Files that are no longer missing (e.g. because they are no longer required) + // should no longer be watched. + onDeleteExistingValue: (missingFilePath, fileWatcher) => { + this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded); + } } ); } @@ -970,7 +975,10 @@ namespace ts.server { } } - type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean }; + interface WildcardDirectoryWatcher { + watcher: FileWatcher; + flags: WatchDirectoryFlags; + } /** * If a file is opened, the server will look for a tsconfig (or jsconfig) @@ -981,7 +989,7 @@ namespace ts.server { private typeAcquisition: TypeAcquisition; /* @internal */ configFileWatcher: FileWatcher; - private directoriesWatchedForWildcards: Map | undefined; + private directoriesWatchedForWildcards: Map | undefined; private typeRootsWatchers: Map | undefined; readonly canonicalConfigFilePath: NormalizedPath; @@ -1136,70 +1144,72 @@ namespace ts.server { } watchWildcards(wildcardDirectories: Map) { - this.directoriesWatchedForWildcards = mutateExistingMap( - this.directoriesWatchedForWildcards, wildcardDirectories, - // Watcher is same if the recursive flags match - ({ recursive: existingRecursive }, flag) => { - // If the recursive dont match, it needs update - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - return existingRecursive !== recursive; - }, - // Create new watch and recursive info - (directory, flag) => { - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - return { - watcher: this.projectService.addDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - recursive - ), - recursive - }; - }, - // Close existing watch thats not needed any more - (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded - ), - // Close existing watch that doesnt match in recursive flag - (directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged - ) + mutateMap( + this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), + wildcardDirectories, + { + // Watcher is same if the recursive flags match + isSameValue: ({ flags: existingFlags }, flags) => existingFlags !== flags, + // Create new watch and recursive info + createNewValue: (directory, flags) => { + return { + watcher: this.projectService.addDirectoryWatcher( + WatchType.WildCardDirectories, this, directory, + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), + flags + ), + flags + }; + }, + // Close existing watch thats not needed any more or doesnt match recursive flags + onDeleteExistingValue: (directory, wildcardDirectoryWatcher, isNotSame) => + this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, isNotSame ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded), + } ); } + private closeWildcardDirectoryWatcher(directory: string, { watcher, flags }: WildcardDirectoryWatcher, closeReason: WatcherCloseReason) { + this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this, directory, watcher, flags, closeReason); + } + stopWatchingWildCards(reason: WatcherCloseReason) { - cleanExistingMap( - this.directoriesWatchedForWildcards, - (directory, { watcher, recursive }) => - this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this, - directory, watcher, recursive, reason) - ); - this.directoriesWatchedForWildcards = undefined; + if (this.directoriesWatchedForWildcards) { + clearMap( + this.directoriesWatchedForWildcards, + (directory, wildcardDirectoryWatcher) => this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, reason) + ); + this.directoriesWatchedForWildcards = undefined; + } } watchTypeRoots() { const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir)); - this.typeRootsWatchers = mutateExistingMapWithNewSet( - this.typeRootsWatchers, newTypeRoots, - // Create new watch - root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root, - path => this.projectService.onTypeRootFileChanged(this, path), /*recursive*/ false - ), - // Close existing watch thats not needed any more - (directory, watcher) => this.projectService.closeDirectoryWatcher( - WatchType.TypeRoot, this, directory, watcher, /*recursive*/ false, WatcherCloseReason.NotNeeded - ) + mutateMap( + this.typeRootsWatchers || (this.typeRootsWatchers = createMap()), + newTypeRoots, + { + // Create new watch + createNewValue: root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root, + path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None + ), + // Close existing watch thats not needed any more + onDeleteExistingValue: (directory, watcher) => this.projectService.closeDirectoryWatcher( + WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded + ) + } ); } stopWatchingTypeRoots(reason: WatcherCloseReason) { - cleanExistingMap( - this.typeRootsWatchers, - (directory, watcher) => - this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this, - directory, watcher, /*recursive*/ false, reason) - ); - this.typeRootsWatchers = undefined; + if (this.typeRootsWatchers) { + clearMap( + this.typeRootsWatchers, + (directory, watcher) => + this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this, + directory, watcher, WatchDirectoryFlags.None, reason) + ); + this.typeRootsWatchers = undefined; + } } close() { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 6c884cebf59..edc3d0e1117 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -293,76 +293,53 @@ namespace ts.server { } } - export function cleanExistingMap( - existingMap: Map, - onDeleteExistingValue: (key: string, existingValue: T) => void) { - if (existingMap) { - // Remove all - existingMap.forEach((existingValue, key) => { - existingMap.delete(key); - onDeleteExistingValue(key, existingValue); - }); - } + /** + * clears already present map (!== undefined) by calling onDeleteExistingValue callback before deleting that key/value + */ + export function clearMap(map: Map, onDeleteExistingValue: (key: string, existingValue: T) => void) { + // Remove all + map.forEach((existingValue, key) => { + map.delete(key); + onDeleteExistingValue(key, existingValue); + }); } - export function mutateExistingMapWithNewSet( - existingMap: Map, newMap: Map, - createNewValue: (key: string) => T, - onDeleteExistingValue: (key: string, existingValue: T) => void - ): Map { - return mutateExistingMap( - existingMap, newMap, - // Same value if the value is set in the map - /*isSameValue*/(_existingValue, _valueInNewMap) => true, - /*createNewValue*/(key, _valueInNewMap) => createNewValue(key), - onDeleteExistingValue, - // Should never be called since we say yes to same values all the time - /*OnDeleteExistingMismatchValue*/(_key, _existingValue) => notImplemented() - ); + export interface MutateMapOptions { + createNewValue(key: string, valueInNewMap: U): T; + onDeleteExistingValue(key: string, existingValue: T, isNotSame?: boolean): void; + + isSameValue?(existingValue: T, valueInNewMap: U): boolean; } - export function mutateExistingMap( - existingMap: Map, newMap: Map, - isSameValue: (existingValue: T, valueInNewMap: U) => boolean, - createNewValue: (key: string, valueInNewMap: U) => T, - onDeleteExistingValue: (key: string, existingValue: T) => void, - OnDeleteExistingMismatchValue: (key: string, existingValue: T) => void - ): Map { + /** + * Mutates the map with newMap such that keys in map will be same as newMap. + */ + export function mutateMap(map: Map, newMap: ReadonlyMap, options: MutateMapOptions) { // If there are new values update them if (newMap) { - if (existingMap) { - // Needs update - existingMap.forEach((existingValue, key) => { - const valueInNewMap = newMap.get(key); - // Existing value - remove it - if (valueInNewMap === undefined) { - existingMap.delete(key); - onDeleteExistingValue(key, existingValue); - } + const { isSameValue, createNewValue, onDeleteExistingValue } = options; + // Needs update + map.forEach((existingValue, key) => { + const valueInNewMap = newMap.get(key); + if (valueInNewMap === undefined || // different value - remove it - else if (!isSameValue(existingValue, valueInNewMap)) { - existingMap.delete(key); - OnDeleteExistingMismatchValue(key, existingValue); - } - }); - } - else { - // Create new - existingMap = createMap(); - } - - // Add new values that are not already present - newMap.forEach((valueInNewMap, key) => { - if (!existingMap.has(key)) { - // New values - existingMap.set(key, createNewValue(key, valueInNewMap)); + (isSameValue && !isSameValue(existingValue, valueInNewMap))) { + const isNotSame = valueInNewMap !== undefined; + map.delete(key); + onDeleteExistingValue(key, existingValue, isNotSame); } }); - return existingMap; + // Add new values that are not already present + newMap.forEach((valueInNewMap, key) => { + if (!map.has(key)) { + // New values + map.set(key, createNewValue(key, valueInNewMap)); + } + }); + } + else { + clearMap(map, options.onDeleteExistingValue); } - - cleanExistingMap(existingMap, onDeleteExistingValue); - return undefined; } } From f723beb24491e51aff667a31663c11e24c8a889c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 055/144] More updates per PR feedback --- src/compiler/core.ts | 13 + .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 2 + src/server/editorServices.ts | 519 +++++++++--------- src/services/refactorProvider.ts | 2 +- 5 files changed, 284 insertions(+), 254 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 54b772cc29c..829a6afa4b7 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -534,6 +534,19 @@ namespace ts { return result; } + export function mapDefinedIter(iter: Iterator, mapFn: (x: T) => U | undefined): U[] { + const result: U[] = []; + while (true) { + const { value, done } = iter.next(); + if (done) break; + const res = mapFn(value); + if (res !== undefined) { + result.push(res); + } + } + return result; + } + /** * Computes the first matching span of elements and returns a tuple of the first span * and the remaining elements. diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index eb2907e89de..3498030142a 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -74,7 +74,7 @@ namespace ts { const projectService = new server.ProjectService(svcOpts); const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined); - const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo); + const project = projectService.assignScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); return { project, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7d70aa74fdf..deaa17c2afe 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -263,6 +263,8 @@ namespace ts.projectSystem { function invokeWatcherCallbacks(callbacks: T[], invokeCallback: (cb: T) => void): void { if (callbacks) { + // The array copy is made to ensure that even if one of the callback removes the callbacks, + // we dont miss any callbacks following it const cbs = callbacks.slice(); for (const cb of cbs) { invokeCallback(cb); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5ca8a54d71f..4d704a365b5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -271,16 +271,16 @@ namespace ts.server { ReloadingFiles = "Reloading configured projects for files", ReloadingInferredRootFiles = "Reloading configured projects for only inferred root files", UpdatedCallback = "Updated the callback", - TrackingFileAdded = "Tracking file added", - TrackingFileRemoved = "Tracking file removed", - InferredRootAdded = "Inferred Root file added", - InferredRootRemoved = "Inferred Root file removed", + OpenFilesImpactedByConfigFileAdd = "File added to open files impacted by this config file", + OpenFilesImpactedByConfigFileRemove = "File removed from open files impacted by this config file", + RootOfInferredProjectTrue = "Open file was set as Inferred root", + RootOfInferredProjectFalse = "Open file was set as not inferred root", } /* @internal */ export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; - interface ConfigFileExistence { + interface ConfigFileExistenceInfo { /** * Cached value of existence of config file * It is true if there is configured project open for this file. @@ -290,18 +290,19 @@ namespace ts.server { */ exists: boolean; /** - * The value in the trackingOpenFilesMap is true if the open file is inferred project root - * and hence tracking changes to this config file - * (either config file is already present but doesnt include the open file in the project structure or config file doesnt exist) - * - * Otherwise its false + * openFilesImpactedByConfigFiles is a map of open files that would be impacted by this config file + * because these are the paths being looked up for their default configured project location + * The value in the map is true if the open file is root of the inferred project + * It is false when the open file that would still be impacted by existance of + * this config file but it is not the root of inferred project */ - trackingOpenFilesMap: Map; + openFilesImpactedByConfigFile: Map; /** - * The file watcher corresponding to this config file for the inferred project root - * The watcher is present only when there is no open configured project for this config file + * The file watcher watching the config file because there is open script info that is root of + * inferred project and will be impacted by change in the status of the config file + * The watcher is present only when there is no open configured project for the config file */ - configFileWatcher?: FileWatcher; + configFileWatcherForRootOfInferredProject?: FileWatcher; } export interface ProjectServiceOptions { @@ -351,6 +352,9 @@ namespace ts.server { private compilerOptionsForInferredProjects: CompilerOptions; private compileOnSaveForInferredProjects: boolean; + /** + * Project size for configured or external projects + */ private readonly projectToSizeMap: Map = createMap(); /** * This is a map of config file paths existance that doesnt need query to disk @@ -359,7 +363,7 @@ namespace ts.server { * - Or it is present if we have configured project open with config file at that location * In this case the exists property is always true */ - private readonly mapOfConfigFilePresence = createMap(); + private readonly mapOfConfigFileExistenceInfo = createMap(); private readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; @@ -427,14 +431,17 @@ namespace ts.server { return this.changedFiles; } + /* @internal */ ensureInferredProjectsUpToDate_TestOnly() { this.ensureProjectStructuresUptoDate(); } + /* @internal */ getCompilerOptionsForInferredProjects() { return this.compilerOptionsForInferredProjects; } + /* @internal */ onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean) { if (!this.eventHandler) { return; @@ -479,14 +486,13 @@ namespace ts.server { const projectName = project.getProjectName(); this.pendingProjectUpdates.set(projectName, project); this.throttledOperations.schedule(projectName, /*delay*/ 250, () => { - const project = this.pendingProjectUpdates.get(projectName); - if (project) { - this.pendingProjectUpdates.delete(projectName); + if (this.pendingProjectUpdates.delete(projectName)) { project.updateGraph(); } }); } + /* @internal */ delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { this.delayUpdateProjectGraph(project); this.delayInferredProjectsRefresh(); @@ -513,7 +519,7 @@ namespace ts.server { this.delayUpdateProjectGraphs(this.inferredProjects); } - findProject(projectName: string): Project { + findProject(projectName: string): Project | undefined { if (projectName === undefined) { return undefined; } @@ -687,39 +693,46 @@ namespace ts.server { } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath); + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath); if (eventKind === FileWatcherEventKind.Deleted) { // Update the cached status - // No action needed on tracking open files since the existing config file anyways didnt affect the tracking file - configFilePresenceInfo.exists = false; + // We arent updating or removing the cached config file presence info as that will be taken care of by + // setConfigFilePresenceByClosedConfigFile when the project is closed (depending on tracking open files) + configFileExistenceInfo.exists = false; this.removeProject(project); // Reload the configured projects for the open files in the map as they are affectected by this config file - this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - // Since the configured project was deleted, we want to reload projects for all the open files - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false); + // Since the configured project was deleted, we want to reload projects for all the open files including files + // that are not root of the inferred project + this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ false); } else { - this.logConfigFileWatchUpdate(project.getConfigFilePath(), configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); + this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles); project.pendingReload = true; this.delayUpdateProjectGraph(project); - // As we scheduled the updated project graph, we would need to only schedule the project reload for the inferred project roots - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ true); + // As we scheduled the update on configured project graph, + // we would need to schedule the project reload for only the root of inferred projects + this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ true); } } /** - * This is the callback function for the config file add/remove/change at any location that matters to open - * script info but doesnt have configured project open for the config file + * This is the callback function for the config file add/remove/change at any location + * that matters to open script info but doesnt have configured project open + * for the config file */ private onConfigFileChangeForOpenScriptInfo(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) { // This callback is called only if we dont have config file project for this config file - const cononicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(cononicalConfigPath); - configFilePresenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.ReloadingFiles); - // The tracking opens files would only contaion the inferred root so no need to check - this.delayReloadConfiguredProjectForFiles(configFilePresenceInfo.trackingOpenFilesMap, /*ignoreIfNotInferredProjectRoot*/ false); + const canonicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigPath); + configFileExistenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigPath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles); + + // Because there is no configured project open for the config file, the tracking open files map + // will only have open files that need the re-detection of the project and hence + // reload projects for all the tracking open files in the map + this.delayReloadConfiguredProjectForFiles(configFileExistenceInfo, /*ignoreIfNotInferredProjectRoot*/ false); } private removeProject(project: Project) { @@ -737,7 +750,7 @@ namespace ts.server { case ProjectKind.Configured: this.configuredProjects.delete((project).canonicalConfigFilePath); this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath); - this.setConfigFilePresenceByClosedConfigFile(project); + this.setConfigFileExistenceInfoByClosedConfiguredProject(project); break; case ProjectKind.Inferred: unorderedRemoveItem(this.inferredProjects, project); @@ -745,47 +758,16 @@ namespace ts.server { } } - private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean): void { - if (info.containingProjects.length === 0) { - // create new inferred project p with the newly opened file as root - // or add root to existing inferred project if 'useOneInferredProject' is true - this.createInferredProjectWithRootFileIfNecessary(info); - - // if useOneInferredProject is not set then try to fixup ownership of open files - // check 'defaultProject !== inferredProject' is necessary to handle cases - // when creation inferred project for some file has added other open files into this project - // (i.e.as referenced files) - // we definitely don't want to delete the project that was just created - // Also note that we need to create a copy of the array since the list of project will change - for (const inferredProject of this.inferredProjects.slice(0, this.inferredProjects.length - 1)) { - Debug.assert(!this.useSingleInferredProject); - // Remove this file from the root of inferred project if its part of more than 2 projects - // This logic is same as iterating over all open files and calling - // this.removRootOfInferredProjectIfNowPartOfOtherProject(f); - // Since this is also called from refreshInferredProject and closeOpen file - // to update inferred projects of the open file, this iteration might be faster - // instead of scanning all open files - const root = inferredProject.getRootScriptInfos(); - Debug.assert(root.length === 1); - if (root[0].containingProjects.length > 1) { - this.removeProject(inferredProject); - } - } - } - else { - for (const p of info.containingProjects) { - // file is the part of configured project - if (p.projectKind === ProjectKind.Configured) { - if (addToListOfOpenFiles) { - ((p)).addOpenRef(); - } - } + private addToListOfOpenFiles(info: ScriptInfo) { + Debug.assert(info.containingProjects.length !== 0); + for (const p of info.containingProjects) { + // file is the part of configured project, addref the project + if (p.projectKind === ProjectKind.Configured) { + ((p)).addOpenRef(); } } - if (addToListOfOpenFiles) { - this.openFiles.push(info); - } + this.openFiles.push(info); } /** @@ -837,17 +819,16 @@ namespace ts.server { this.removeProject(project); } - // collect orphaned files and try to re-add them as newly opened - // treat orphaned files as newly opened - // for all open files + // collect orphaned files and assign them to inferred project just like we treat open of a file for (const f of this.openFiles) { if (f.containingProjects.length === 0) { - this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false); + this.assignScriptInfoToInferredProject(f); } } - // Cleanup script infos that arent part of any project is postponed to - // next file open so that if file from same project is opened we wont end up creating same script infos + // Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project) + // is postponed to next file open so that if file from same project is opened, + // we wont end up creating same script infos } // If the current info is being just closed - add the watcher file to track changes @@ -871,15 +852,15 @@ namespace ts.server { } private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { - let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (configFilePresenceInfo) { - // By default the info is belong to the config file. - // Only adding the info as a root to inferred project will make it the root - if (!configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) { - configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + let configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + if (configFileExistenceInfo) { + // By default the info would get impacted by presence of config file since its in the detection path + // Only adding the info as a root to inferred project will need the existence to be watched by file watcher + if (!configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { + configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd); } - return configFilePresenceInfo.exists; + return configFileExistenceInfo.exists; } // Theoretically we should be adding watch for the directory here itself. @@ -887,162 +868,148 @@ namespace ts.server { // somewhere inside the another config file directory. // And technically we could handle that case in configFile's directory watcher in some cases // But given that its a rare scenario it seems like too much overhead. (we werent watching those directories earlier either) - // So what we are now watching is: configFile if the project is open - // And the whole chain of config files only for the inferred project roots - // Cache the host value of file exists and add the info tio to the tracked root - const trackingOpenFilesMap = createMap(); - trackingOpenFilesMap.set(info.path, false); + // So what we are now watching is: configFile if the configured project corresponding to it is open + // Or the whole chain of config files for the roots of the inferred projects + + // Cache the host value of file exists and add the info to map of open files impacted by this config file + const openFilesImpactedByConfigFile = createMap(); + openFilesImpactedByConfigFile.set(info.path, false); const exists = this.host.fileExists(configFileName); - configFilePresenceInfo = { exists, trackingOpenFilesMap }; - this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileAdded); + configFileExistenceInfo = { exists, openFilesImpactedByConfigFile }; + this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFileExistenceInfo); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd); return exists; } - private setConfigFilePresenceByNewConfiguredProject(project: ConfiguredProject) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(project.canonicalConfigFilePath); - if (configFilePresenceInfo) { - Debug.assert(configFilePresenceInfo.exists); + private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) { + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath); + if (configFileExistenceInfo) { + Debug.assert(configFileExistenceInfo.exists); // close existing watcher - if (configFilePresenceInfo.configFileWatcher) { + if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) { const configFileName = project.getConfigFilePath(); this.closeFileWatcher( WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFilePresenceInfo.configFileWatcher, WatcherCloseReason.ConfigProjectCreated + configFileExistenceInfo.configFileWatcherForRootOfInferredProject, WatcherCloseReason.ConfigProjectCreated ); - configFilePresenceInfo.configFileWatcher = undefined; - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; + this.logConfigFileWatchUpdate(configFileName, project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } } else { - // We could be in this scenario if it is the external project tracked configured file + // We could be in this scenario if project is the configured project tracked by external project // Since that route doesnt check if the config file is present or not - this.mapOfConfigFilePresence.set(project.canonicalConfigFilePath, { + this.mapOfConfigFileExistenceInfo.set(project.canonicalConfigFilePath, { exists: true, - trackingOpenFilesMap: createMap() + openFilesImpactedByConfigFile: createMap() }); } } - private configFileExistenceTracksInferredRoot(configFilePresenceInfo: ConfigFileExistence) { - return forEachEntry(configFilePresenceInfo.trackingOpenFilesMap, (value, __key) => value); + /** + * Returns true if the configFileExistenceInfo is needed/impacted by open files that are root of inferred project + */ + private configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo: ConfigFileExistenceInfo) { + return forEachEntry(configFileExistenceInfo.openFilesImpactedByConfigFile, (isRootOfInferredProject, __key) => isRootOfInferredProject); } - private setConfigFilePresenceByClosedConfigFile(closedProject: ConfiguredProject) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(closedProject.canonicalConfigFilePath); - Debug.assert(!!configFilePresenceInfo); - const trackingOpenFilesMap = configFilePresenceInfo.trackingOpenFilesMap; - if (trackingOpenFilesMap.size) { + private setConfigFileExistenceInfoByClosedConfiguredProject(closedProject: ConfiguredProject) { + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(closedProject.canonicalConfigFilePath); + Debug.assert(!!configFileExistenceInfo); + if (configFileExistenceInfo.openFilesImpactedByConfigFile.size) { const configFileName = closedProject.getConfigFilePath(); - if (this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) { - Debug.assert(!configFilePresenceInfo.configFileWatcher); - configFilePresenceInfo.configFileWatcher = this.addFileWatcher( + // If there are open files that are impacted by this config file existence + // but none of them are root of inferred project, the config file watcher will be + // created when any of the script infos are added as root of inferred project + if (this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { + Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher( WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) ); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.logConfigFileWatchUpdate(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } } else { - // There is no one tracking anymore. Remove the status - this.mapOfConfigFilePresence.delete(closedProject.canonicalConfigFilePath); + // There is not a single file open thats tracking the status of this config file. Remove from cache + this.mapOfConfigFileExistenceInfo.delete(closedProject.canonicalConfigFilePath); } } - private logConfigFileWatchUpdate(configFileName: NormalizedPath, configFilePresenceInfo: ConfigFileExistence, status: ConfigFileWatcherStatus) { + private logConfigFileWatchUpdate(configFileName: NormalizedPath, canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo, status: ConfigFileWatcherStatus) { if (!this.logger.loggingEnabled()) { return; } const inferredRoots: string[] = []; const otherFiles: string[] = []; - configFilePresenceInfo.trackingOpenFilesMap.forEach((value, key: Path) => { - const info = this.getScriptInfoForPath(key); - if (value) { - inferredRoots.push(info.fileName); - } - else { - otherFiles.push(info.fileName); - } + configFileExistenceInfo.openFilesImpactedByConfigFile.forEach((isRootOfInferredProject, key) => { + const info = this.getScriptInfoForPath(key as Path); + (isRootOfInferredProject ? inferredRoots : otherFiles).push(info.fileName); }); - const watchType = status === ConfigFileWatcherStatus.UpdatedCallback || - status === ConfigFileWatcherStatus.ReloadingFiles || - status === ConfigFileWatcherStatus.ReloadingInferredRootFiles ? - (configFilePresenceInfo.configFileWatcher ? WatchType.ConfigFileForInferredRoot : WatchType.ConfigFilePath) : - ""; - this.logger.info(`ConfigFilePresence ${watchType}:: File: ${configFileName} Currently Tracking: InferredRootFiles: ${inferredRoots} OtherFiles: ${otherFiles} Status: ${status}`); + + const watches: WatchType[] = []; + if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) { + watches.push(WatchType.ConfigFileForInferredRoot); + } + if (this.configuredProjects.has(canonicalConfigFilePath)) { + watches.push(WatchType.ConfigFilePath); + } + this.logger.info(`ConfigFilePresence:: Current Watches: ['${watches.join("','")}']:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } - private closeConfigFileWatcherIfInferredRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, - configFilePresenceInfo: ConfigFileExistence, infoIsInferredRoot: boolean, reason: WatcherCloseReason) { - // Close the config file watcher if it was the last inferred root - if (infoIsInferredRoot && - configFilePresenceInfo.configFileWatcher && - !this.configFileExistenceTracksInferredRoot(configFilePresenceInfo)) { + /** + * Close the config file watcher in the cached ConfigFileExistenceInfo + * if there arent any open files that are root of inferred project + */ + private closeConfigFileWatcherOfConfigFileExistenceInfo( + configFileName: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo, + reason: WatcherCloseReason + ) { + // Close the config file watcher if there are no more open files that are root of inferred project + if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject && + !this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { this.closeFileWatcher( WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFilePresenceInfo.configFileWatcher, reason - ); - configFilePresenceInfo.configFileWatcher = undefined; - } - - // If this was the last tracking file open for this config file, remove the cached value - if (!configFilePresenceInfo.trackingOpenFilesMap.size && - !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { - this.mapOfConfigFilePresence.delete(canonicalConfigFilePath); - } - } - - private closeConfigFileWatchForClosedScriptInfo(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (configFilePresenceInfo) { - const isInferredRoot = configFilePresenceInfo.trackingOpenFilesMap.get(info.path); - - // Delete the info from tracking - configFilePresenceInfo.trackingOpenFilesMap.delete(info.path); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.TrackingFileRemoved); - - // Close the config file watcher if it was the last inferred root - this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath, - configFilePresenceInfo, isInferredRoot, WatcherCloseReason.FileClosed + configFileExistenceInfo.configFileWatcherForRootOfInferredProject, reason ); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; } } /** * This is called on file close, so that we stop watching the config file for this script info - * @param info */ private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.isScriptOpen()); - this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => - this.closeConfigFileWatchForClosedScriptInfo(configFileName, canonicalConfigFilePath, info) - ); - } + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + if (configFileExistenceInfo) { + const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path); - private watchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { - let configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (!configFilePresenceInfo) { - // Create the cache - configFilePresenceInfo = { - exists: this.host.fileExists(configFileName), - trackingOpenFilesMap: createMap() - }; - this.mapOfConfigFilePresence.set(canonicalConfigFilePath, configFilePresenceInfo); - } + // Delete the info from map, since this file is no more open + configFileExistenceInfo.openFilesImpactedByConfigFile.delete(info.path); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileRemove); - // Set this file as inferred root - configFilePresenceInfo.trackingOpenFilesMap.set(info.path, true); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootAdded); + // If the script info was not root of inferred project, + // there wont be config file watch open because of this script info + if (infoIsRootOfInferredProject) { + // But if it is a root, it could be the last script info that is root of inferred project + // and hence we would need to close the config file watcher + this.closeConfigFileWatcherOfConfigFileExistenceInfo( + configFileName, configFileExistenceInfo, WatcherCloseReason.FileClosed + ); + } - // If there is no configured project for this config file, create the watcher - if (!configFilePresenceInfo.configFileWatcher && - !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { - configFilePresenceInfo.configFileWatcher = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); - } + // If there are no open files that are impacted by configFileExistenceInfo after closing this script info + // there is no configured project present, remove the cached existence info + if (!configFileExistenceInfo.openFilesImpactedByConfigFile.size && + !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { + Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject); + this.mapOfConfigFileExistenceInfo.delete(canonicalConfigFilePath); + } + } + }); } /** @@ -1051,25 +1018,30 @@ namespace ts.server { /* @internal */ startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); - this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => - this.watchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info) - ); - } - - private closeWatchConfigFileForInferredProjectRoot(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo, reason: WatcherCloseReason) { - const configFilePresenceInfo = this.mapOfConfigFilePresence.get(canonicalConfigFilePath); - if (configFilePresenceInfo) { - // Set this as not inferred root - if (configFilePresenceInfo.trackingOpenFilesMap.has(info.path)) { - configFilePresenceInfo.trackingOpenFilesMap.set(info.path, false); - this.logConfigFileWatchUpdate(configFileName, configFilePresenceInfo, ConfigFileWatcherStatus.InferredRootRemoved); + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { + let configFilePresenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + if (!configFilePresenceInfo) { + // Create the cache + configFilePresenceInfo = { + exists: this.host.fileExists(configFileName), + openFilesImpactedByConfigFile: createMap() + }; + this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFilePresenceInfo); } - // Close the watcher if present - this.closeConfigFileWatcherIfInferredRoot(configFileName, canonicalConfigFilePath, - configFilePresenceInfo, /*infoIsInferredRoot*/ true, reason - ); - } + // Set this file as the root of inferred project + configFilePresenceInfo.openFilesImpactedByConfigFile.set(info.path, true); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue); + + // If there is no configured project for this config file, add the file watcher + if (!configFilePresenceInfo.configFileWatcherForRootOfInferredProject && + !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { + configFilePresenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, + (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) + ); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + }); } /** @@ -1077,9 +1049,21 @@ namespace ts.server { */ /* @internal */ stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { - this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => - this.closeWatchConfigFileForInferredProjectRoot(configFileName, canonicalConfigFilePath, info, reason) - ); + this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { + const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { + Debug.assert(info.isScriptOpen()); + + // Info is not root of inferred project any more + configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, false); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectFalse); + + // Close the config file watcher + this.closeConfigFileWatcherOfConfigFileExistenceInfo( + configFileName, configFileExistenceInfo, reason + ); + } + }); } /** @@ -1165,7 +1149,6 @@ namespace ts.server { function printProjects(logger: Logger, projects: Project[], counter: number) { for (const project of projects) { - // Print shouldnt update the graph. It should emit whatever state the project is currently in logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); logger.info(project.filesToString()); logger.info("-----------------------------------------------"); @@ -1175,13 +1158,13 @@ namespace ts.server { } } - private findConfiguredProjectByProjectName(configFileName: NormalizedPath) { + private findConfiguredProjectByProjectName(configFileName: NormalizedPath): ConfiguredProject | undefined { // make sure that casing of config file name is consistent const canonicalConfigFilePath = asNormalizedPath(this.toCanonicalFileName(configFileName)); return this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath); } - private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string) { + private getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath: string): ConfiguredProject | undefined { return this.configuredProjects.get(canonicalConfigFilePath); } @@ -1259,7 +1242,7 @@ namespace ts.server { return false; } - private createAndAddExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) { + private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition) { const compilerOptions = convertCompilerOptions(options); const project = new ExternalProject( projectFileName, @@ -1319,7 +1302,18 @@ namespace ts.server { } } - private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) { + private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { + project.setProjectErrors(configFileErrors); + this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName); + project.setTypeAcquisition(typeAcquisition); + // This doesnt need scheduling since its either creation or reload of the project + project.updateGraph(); + } + + private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) { + const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName); + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); + this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( configFileName, @@ -1332,7 +1326,7 @@ namespace ts.server { cachedServerHost); project.configFileSpecs = configFileSpecs; - // TODO: (sheetalkamat) We should also watch the configFiles that are extended + // TODO: We probably should also watch the configFiles that are extended project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind) ); @@ -1343,26 +1337,11 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); this.configuredProjects.set(project.canonicalConfigFilePath, project); - this.setConfigFilePresenceByNewConfiguredProject(project); + this.setConfigFileExistenceByNewConfiguredProject(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; } - private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { - project.setProjectErrors(configFileErrors); - this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName); - project.setTypeAcquisition(typeAcquisition); - // This doesnt need scheduling since its either creation or reload of the project - project.updateGraph(); - } - - private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) { - const cachedServerHost = new CachedServerHost(this.host, this.toCanonicalFileName); - const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); - this.logger.info(`Opened configuration file ${configFileName}`); - return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, cachedServerHost, clientFileName); - } - private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, clientFileName?: string) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap: Map = createMap(); @@ -1474,18 +1453,44 @@ namespace ts.server { this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } - createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) { + /*@internal*/ + assignScriptInfoToInferredProject(info: ScriptInfo) { + Debug.assert(info.containingProjects.length === 0); + + // create new inferred project p with the newly opened file as root + // or add root to existing inferred project if 'useSingleInferredProject' is true const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length; const project = useExistingProject ? this.inferredProjects[0] : new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects); - project.addRoot(root); + project.addRoot(info); project.updateGraph(); if (!useExistingProject) { + // Add the new project to inferred projects list this.inferredProjects.push(project); + + for (const inferredProject of this.inferredProjects.slice(0, this.inferredProjects.length - 1)) { + // Note that we need to create a copy of the array since the list of project can change + Debug.assert(inferredProject !== project); + + // Remove the inferred project if the root of it is now part of newly created inferred project + // e.g through references + // Which means if any root of inferred project is part of more than 1 project can be removed + // This logic is same as iterating over all open files and calling + // this.removeRootOfInferredProjectIfNowPartOfOtherProject(f); + // Since this is also called from refreshInferredProject and closeOpen file + // to update inferred projects of the open file, this iteration might be faster + // instead of scanning all open files + const root = inferredProject.getRootScriptInfos(); + Debug.assert(root.length === 1); + if (root[0].containingProjects.length > 1) { + this.removeProject(inferredProject); + } + } } + return project; } @@ -1632,28 +1637,33 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); - this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false); + this.reloadConfiguredsProjectForFiles(this.openFiles, /*delayReload*/ false); this.refreshInferredProjects(); } - delayReloadConfiguredProjectForFiles(openFilesMap: Map, ignoreIfNotInferredProjectRoot: boolean) { + private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) { // Get open files to reload projects for - const openFiles = flatMapIter(openFilesMap.keys(), path => { - if (!ignoreIfNotInferredProjectRoot || openFilesMap.get(path)) { - return this.getScriptInfoForPath(path as Path); + const openFiles = mapDefinedIter( + configFileExistenceInfo.openFilesImpactedByConfigFile.entries(), + ([path, isRootOfInferredProject]) => { + if (!ignoreIfNotRootOfInferredProject || isRootOfInferredProject) { + const info = this.getScriptInfoForPath(path as Path); + Debug.assert(!!info); + return info; + } } - }); - this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); + ); + this.reloadConfiguredsProjectForFiles(openFiles, /*delayReload*/ true); this.delayInferredProjectsRefresh(); } /** * This function goes through all the openFiles and tries to file the config file for them. * If the config file is found and it refers to existing project, it reloads it either immediately - * or schedules it for reload depending on delayedReload option + * or schedules it for reload depending on delayReload option * If the there is no existing project it just opens the configured project for the config file */ - reloadConfiguredProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) { + private reloadConfiguredsProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) { const mapUpdatedProjects = createMap(); // try to reload config file for all open files for (const info of openFiles) { @@ -1665,7 +1675,7 @@ namespace ts.server { if (configFileName) { let project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - project = this.openConfigFile(configFileName, info.fileName); + project = this.createConfiguredProject(configFileName, info.fileName); mapUpdatedProjects.set(configFileName, true); } else if (!mapUpdatedProjects.has(configFileName)) { @@ -1716,7 +1726,7 @@ namespace ts.server { for (const info of this.openFiles) { // collect all orphaned script infos from open files if (info.containingProjects.length === 0) { - this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ false); + this.assignScriptInfoToInferredProject(info); } // Or remove the root of inferred project if is referenced in more than one projects else { @@ -1752,7 +1762,7 @@ namespace ts.server { if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - project = this.openConfigFile(configFileName, fileName); + project = this.createConfiguredProject(configFileName, fileName); // even if opening config file was successful, it could still // contain errors that were tolerated. @@ -1771,8 +1781,13 @@ namespace ts.server { project.markAsDirty(); } - // at this point if file is the part of some configured/external project then this project should be created - this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true); + // At this point if file is part of any any configured or external project, then it would be present in the containing projects + // So if it still doesnt have any containing projects, it needs to be part of inferred project + if (info.containingProjects.length === 0) { + this.assignScriptInfoToInferredProject(info); + } + this.addToListOfOpenFiles(info); + // Delete the orphan files here because there might be orphan script infos (which are not part of project) // when some file/s were closed which resulted in project removal. // It was then postponed to cleanup these script infos so that they can be reused if @@ -2084,7 +2099,7 @@ namespace ts.server { let project = this.findConfiguredProjectByProjectName(tsconfigFile); if (!project) { // errors are stored in the project - project = this.openConfigFile(tsconfigFile); + project = this.createConfiguredProject(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 @@ -2095,7 +2110,7 @@ namespace ts.server { else { // no config files - remove the item from the collection this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName); - this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); + this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition); } if (!suppressRefreshOfInferredProjects) { this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true); diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 432df8c53d0..04b12f16563 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -35,7 +35,7 @@ namespace ts { export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { return flatMapIter(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() ? [] : refactor.getAvailableActions(context)); + context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context)); } export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { From b071a8610c4fc86c685605965a42047bb4b41e27 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 7 Aug 2017 14:47:32 -0700 Subject: [PATCH 056/144] More work on feedback from PR --- src/server/editorServices.ts | 85 +++++++++++++++++------------------- src/server/project.ts | 11 +++-- src/server/utilities.ts | 5 +++ 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4d704a365b5..27dbee2ebf5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -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(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, clientFileName?: string) { const projectRootFilesMap = project.getRootFilesMap(); - const newRootScriptInfoMap: Map = createMap(); + const newRootScriptInfoMap = createMap(); 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)) { 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 if (projectRootFilesMap.size > newRootScriptInfoMap.size) { projectRootFilesMap.forEach((value, path) => { @@ -1388,12 +1388,14 @@ namespace ts.server { } 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(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, 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}`); + + // Read updated contents from disk const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); + + // Update the project project.configFileSpecs = configFileSpecs; - this.updateConfiguredProject(project, projectOptions, configFileErrors); - - if (!this.eventHandler) { - return; - } - - this.eventHandler({ - eventName: ConfigFileDiagEvent, - data: { configFileName, diagnostics: project.getGlobalProjectErrors() || [], triggerFile: configFileName } - }); - } - - /** - * Updates the configured project with updated config file contents - * @param project - */ - private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) { 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({ + 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) { - const mapUpdatedProjects = createMap(); + const updatedProjects = createMap(); // 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,12 +1668,12 @@ 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); @@ -1686,19 +1681,22 @@ namespace ts.server { 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 */ private removeRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { + // If the script info is root of inferred project, it could only be first containing 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) { 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); } diff --git a/src/server/project.ts b/src/server/project.ts index 06e4205bc8f..a8506982bec 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -487,13 +487,12 @@ namespace ts.server { // add a root file to project addRoot(info: ScriptInfo) { - if (!this.isRoot(info)) { - this.rootFiles.push(info); - this.rootFilesMap.set(info.path, info); - info.attachToProject(this); + Debug.assert(!this.isRoot(info)); + this.rootFiles.push(info); + this.rootFilesMap.set(info.path, info); + info.attachToProject(this); - this.markAsDirty(); - } + this.markAsDirty(); } // add a root file to project diff --git a/src/server/utilities.ts b/src/server/utilities.ts index edc3d0e1117..79065f48ff5 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -227,6 +227,11 @@ namespace ts.server { /* @internal */ namespace ts.server { + export function getBaseConfigFileName(configFilePath: NormalizedPath): "tsconfig.json" | "jsconfig.json" | undefined { + const base = getBaseFileName(configFilePath); + return base === "tsconfig.json" || base === "jsconfig.json" ? base : undefined; + } + export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { if (array.length === 0) { array.push(insert); From 8db05c2d854faa7e92a655084281d39798ba88fd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 11 Aug 2017 16:37:47 -0700 Subject: [PATCH 057/144] More work on PR feedback update --- src/compiler/commandLineParser.ts | 7 ++-- src/compiler/utilities.ts | 4 --- .../unittests/tsserverProjectSystem.ts | 2 +- src/server/editorServices.ts | 34 +++++++++---------- src/server/project.ts | 13 ++++--- src/server/utilities.ts | 21 +++++++----- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 21cf4275cca..fb92f59d12d 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1482,6 +1482,7 @@ namespace ts { return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; } + /*@internal*/ export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName: string | undefined) { return createCompilerDiagnostic( Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, @@ -1928,9 +1929,9 @@ namespace ts { /** * 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 filesSpecs The literal file names to include. + * @param includeSpecs The wildcard file specifications to include. + * @param excludeSpecs 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. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 14cab78bc84..76d242e8b08 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3607,10 +3607,6 @@ namespace ts { export function getCombinedLocalAndExportSymbolFlags(symbol: Symbol): SymbolFlags { return symbol.exportSymbol ? symbol.exportSymbol.flags | symbol.flags : symbol.flags; } - - export function isRecursiveDirectoryWatch(flags: WatchDirectoryFlags) { - return (flags & WatchDirectoryFlags.Recursive) !== 0; - } } namespace ts { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index deaa17c2afe..f4513cf7113 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -534,7 +534,7 @@ namespace ts.projectSystem { } /** - * This will call the directory watcher for the foldeFullPath and recursive directory watchers for this and base folders + * This will call the directory watcher for the folderFullPath and recursive directory watchers for this and base folders */ private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 27dbee2ebf5..bf480e355fe 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -246,7 +246,7 @@ namespace ts.server { export const enum WatchType { ConfigFilePath = "Config file for the program", MissingFilePath = "Missing file from program", - WildCardDirectories = "Wild card directory", + WildcardDirectories = "Wild card directory", TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", ConfigFileForInferredRoot = "Config file for the inferred project root" @@ -363,7 +363,7 @@ namespace ts.server { * - Or it is present if we have configured project open with config file at that location * In this case the exists property is always true */ - private readonly mapOfConfigFileExistenceInfo = createMap(); + private readonly configFileExistenceInfoCache = createMap(); private readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; @@ -693,7 +693,7 @@ namespace ts.server { } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath); if (eventKind === FileWatcherEventKind.Deleted) { // Update the cached status // We arent updating or removing the cached config file presence info as that will be taken care of by @@ -725,7 +725,7 @@ namespace ts.server { private onConfigFileChangeForOpenScriptInfo(configFileName: NormalizedPath, eventKind: FileWatcherEventKind) { // This callback is called only if we dont have config file project for this config file const canonicalConfigPath = normalizedPathToPath(configFileName, this.currentDirectory, this.toCanonicalFileName); - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigPath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigPath); configFileExistenceInfo.exists = (eventKind !== FileWatcherEventKind.Deleted); this.logConfigFileWatchUpdate(configFileName, canonicalConfigPath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingFiles); @@ -852,7 +852,7 @@ namespace ts.server { } private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { - let configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { // By default the info would get impacted by presence of config file since its in the detection path // Only adding the info as a root to inferred project will need the existence to be watched by file watcher @@ -877,13 +877,13 @@ namespace ts.server { openFilesImpactedByConfigFile.set(info.path, false); const exists = this.host.fileExists(configFileName); configFileExistenceInfo = { exists, openFilesImpactedByConfigFile }; - this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFileExistenceInfo); + this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo); this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.OpenFilesImpactedByConfigFileAdd); return exists; } private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) { - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(project.canonicalConfigFilePath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath); if (configFileExistenceInfo) { Debug.assert(configFileExistenceInfo.exists); // close existing watcher @@ -900,7 +900,7 @@ namespace ts.server { else { // We could be in this scenario if project is the configured project tracked by external project // Since that route doesnt check if the config file is present or not - this.mapOfConfigFileExistenceInfo.set(project.canonicalConfigFilePath, { + this.configFileExistenceInfoCache.set(project.canonicalConfigFilePath, { exists: true, openFilesImpactedByConfigFile: createMap() }); @@ -915,7 +915,7 @@ namespace ts.server { } private setConfigFileExistenceInfoByClosedConfiguredProject(closedProject: ConfiguredProject) { - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(closedProject.canonicalConfigFilePath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(closedProject.canonicalConfigFilePath); Debug.assert(!!configFileExistenceInfo); if (configFileExistenceInfo.openFilesImpactedByConfigFile.size) { const configFileName = closedProject.getConfigFilePath(); @@ -933,7 +933,7 @@ namespace ts.server { } else { // There is not a single file open thats tracking the status of this config file. Remove from cache - this.mapOfConfigFileExistenceInfo.delete(closedProject.canonicalConfigFilePath); + this.configFileExistenceInfoCache.delete(closedProject.canonicalConfigFilePath); } } @@ -983,7 +983,7 @@ namespace ts.server { private stopWatchingConfigFilesForClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.isScriptOpen()); this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { const infoIsRootOfInferredProject = configFileExistenceInfo.openFilesImpactedByConfigFile.get(info.path); @@ -1006,7 +1006,7 @@ namespace ts.server { if (!configFileExistenceInfo.openFilesImpactedByConfigFile.size && !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject); - this.mapOfConfigFileExistenceInfo.delete(canonicalConfigFilePath); + this.configFileExistenceInfoCache.delete(canonicalConfigFilePath); } } }); @@ -1019,14 +1019,14 @@ namespace ts.server { startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { - let configFilePresenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + let configFilePresenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (!configFilePresenceInfo) { // Create the cache configFilePresenceInfo = { exists: this.host.fileExists(configFileName), openFilesImpactedByConfigFile: createMap() }; - this.mapOfConfigFileExistenceInfo.set(canonicalConfigFilePath, configFilePresenceInfo); + this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFilePresenceInfo); } // Set this file as the root of inferred project @@ -1050,7 +1050,7 @@ namespace ts.server { /* @internal */ stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { - const configFileExistenceInfo = this.mapOfConfigFileExistenceInfo.get(canonicalConfigFilePath); + const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { Debug.assert(info.isScriptOpen()); @@ -1607,14 +1607,14 @@ namespace ts.server { /* @internal */ closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, flags: WatchDirectoryFlags, reason: WatcherCloseReason) { - const recursive = isRecursiveDirectoryWatch(flags); + const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; 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, flags: WatchDirectoryFlags) { - const recursive = isRecursiveDirectoryWatch(flags); + const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; 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)); diff --git a/src/server/project.ts b/src/server/project.ts index a8506982bec..e2b536017c5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1153,22 +1153,25 @@ namespace ts.server { createNewValue: (directory, flags) => { return { watcher: this.projectService.addDirectoryWatcher( - WatchType.WildCardDirectories, this, directory, + WatchType.WildcardDirectories, this, directory, path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), flags ), flags }; }, - // Close existing watch thats not needed any more or doesnt match recursive flags - onDeleteExistingValue: (directory, wildcardDirectoryWatcher, isNotSame) => - this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, isNotSame ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded), + // Close existing watch thats not needed any more + onDeleteExistingValue: (directory, wildcardDirectoryWatcher) => + this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, WatcherCloseReason.NotNeeded), + // Close existing watch that doesnt match in the recursive flags + onDeleteExistingMismatchValue: (directory, wildcardDirectoryWatcher) => + this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, WatcherCloseReason.RecursiveChanged), } ); } private closeWildcardDirectoryWatcher(directory: string, { watcher, flags }: WildcardDirectoryWatcher, closeReason: WatcherCloseReason) { - this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this, directory, watcher, flags, closeReason); + this.projectService.closeDirectoryWatcher(WatchType.WildcardDirectories, this, directory, watcher, flags, closeReason); } stopWatchingWildCards(reason: WatcherCloseReason) { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 79065f48ff5..0699b4c9b6c 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -299,14 +299,14 @@ namespace ts.server { } /** - * clears already present map (!== undefined) by calling onDeleteExistingValue callback before deleting that key/value + * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ export function clearMap(map: Map, onDeleteExistingValue: (key: string, existingValue: T) => void) { // Remove all map.forEach((existingValue, key) => { - map.delete(key); onDeleteExistingValue(key, existingValue); }); + map.clear(); } export interface MutateMapOptions { @@ -314,6 +314,7 @@ namespace ts.server { onDeleteExistingValue(key: string, existingValue: T, isNotSame?: boolean): void; isSameValue?(existingValue: T, valueInNewMap: U): boolean; + onDeleteExistingMismatchValue?(key: string, existingValue: T): void; } /** @@ -322,16 +323,20 @@ namespace ts.server { export function mutateMap(map: Map, newMap: ReadonlyMap, options: MutateMapOptions) { // If there are new values update them if (newMap) { - const { isSameValue, createNewValue, onDeleteExistingValue } = options; + const { isSameValue, createNewValue, onDeleteExistingValue, onDeleteExistingMismatchValue } = options; // Needs update map.forEach((existingValue, key) => { const valueInNewMap = newMap.get(key); - if (valueInNewMap === undefined || - // different value - remove it - (isSameValue && !isSameValue(existingValue, valueInNewMap))) { - const isNotSame = valueInNewMap !== undefined; + // Not present any more in new map, remove it + if (valueInNewMap === undefined) { map.delete(key); - onDeleteExistingValue(key, existingValue, isNotSame); + onDeleteExistingValue(key, existingValue); + } + // different value - remove it + else if (isSameValue && !isSameValue(existingValue, valueInNewMap)) { + Debug.assert(!!onDeleteExistingMismatchValue); + map.delete(key); + onDeleteExistingMismatchValue(key, existingValue); } }); From 59d07dc4888da2e7ba9c1d9e8529b9141d552473 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 11:27:02 -0700 Subject: [PATCH 058/144] Simplified mutate map options --- src/compiler/builder.ts | 6 +++--- src/compiler/program.ts | 38 ++++++++++++++++++++++---------------- src/compiler/utilities.ts | 31 ++++++++++++------------------- src/server/project.ts | 2 +- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 19a18f7b9c1..849732f0afb 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -104,9 +104,9 @@ namespace ts { // Add new file info createNewValue: (_path, sourceFile) => addNewFileInfo(program, sourceFile), // Remove existing file info - onDeleteExistingValue: removeExistingFileInfo, + onDeleteValue: removeExistingFileInfo, // We will update in place instead of deleting existing value and adding new one - onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) + onExistingValue: (_key, existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) } ); } @@ -396,7 +396,7 @@ namespace ts { createNewValue: (key): true => { referencedBy.add(key, path); return true; }, // Remove existing reference by entry: source file doesnt reference file 'key' any more // in other words source file (path) is not referenced by 'key' - onDeleteExistingValue: (key, _existingValue) => { referencedBy.remove(key, path); } + onDeleteValue: (key, _existingValue) => { referencedBy.remove(key, path); } } ); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 142d6351a46..c642d99ff9d 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -473,7 +473,7 @@ namespace ts { createNewValue: createMissingFileWatch, // Files that are no longer missing (e.g. because they are no longer required) // should no longer be watched. - onDeleteExistingValue: closeExistingMissingFilePathFileWatcher + onDeleteValue: closeExistingMissingFilePathFileWatcher } ); } @@ -497,26 +497,32 @@ namespace ts { wildcardDirectories, { // Create new watch and recursive info - createNewValue: (directory, flags) => { - return { - watcher: watchDirectory(directory, flags), - flags - }; - }, + createNewValue: createWildcardDirectoryWatcher, // Close existing watch thats not needed any more - onDeleteExistingValue: (directory, wildcardDirectoryWatcher) => + onDeleteValue: (directory, wildcardDirectoryWatcher) => closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ false), // Close existing watch that doesnt match in the flags - shouldDeleteExistingValue: (directory, wildcardDirectoryWatcher, flags) => { - // Watcher is same if the recursive flags match - if (wildcardDirectoryWatcher.flags === flags) { - return false; - } - closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true); - return true; - } + onExistingValue: updateWildcardDirectoryWatcher } ); + + function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatchers { + // Create new watch and recursive info + return { + watcher: watchDirectory(directory, flags), + flags + }; + } + + function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatchers, flags: WatchDirectoryFlags) { + // Watcher needs to be updated if the recursive flags dont match + if (wildcardDirectoryWatcher.flags === flags) { + return; + } + + closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true); + existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); + } } /** diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3f0fae850d6..6e26a287b83 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3491,27 +3491,25 @@ namespace ts { /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ - export function clearMap(map: Map, onDeleteExistingValue: (key: string, existingValue: T) => void) { + export function clearMap(map: Map, onDeleteValue: (key: string, existingValue: T) => void) { // Remove all map.forEach((existingValue, key) => { - onDeleteExistingValue(key, existingValue); + onDeleteValue(key, existingValue); }); map.clear(); } export interface MutateMapOptions { createNewValue(key: string, valueInNewMap: U): T; - onDeleteExistingValue(key: string, existingValue: T, isNotSame?: boolean): void; + onDeleteValue(key: string, existingValue: T): void; /** - * If present this is called if there is value for the key in new map and existing map + * If present this is called with the key when there is value for that key both in new map as well as existing map provided + * Caller can then decide to update or remove this key. + * If the key is removed, caller will get callback of createNewValue for that key. + * If this callback is not provided, the value of such keys is not updated. */ - onExistingValue?(existingValue: T, valueInNewMap: U): void; - /** - * If there onExistingValue is not provided, this callback if present will be called to - * detemine if the value in the map needs to be deleted - */ - shouldDeleteExistingValue?(key: string, existingValue: T, valueInNewMap: U): boolean; + onExistingValue?(key: string, existingValue: T, valueInNewMap: U): void; } /** @@ -3520,23 +3518,18 @@ namespace ts { export function mutateMap(map: Map, newMap: ReadonlyMap, options: MutateMapOptions) { // If there are new values update them if (newMap) { - const { createNewValue, onDeleteExistingValue, onExistingValue, shouldDeleteExistingValue } = options; + const { createNewValue, onDeleteValue, onExistingValue } = options; // Needs update map.forEach((existingValue, key) => { const valueInNewMap = newMap.get(key); // Not present any more in new map, remove it if (valueInNewMap === undefined) { map.delete(key); - onDeleteExistingValue(key, existingValue); + onDeleteValue(key, existingValue); } // If present notify about existing values else if (onExistingValue) { - onExistingValue(existingValue, valueInNewMap); - } - // different value, delete it here if this value cant be kept around - // Note that if the value is deleted here, new value will be created in newMap.forEach loop for this key - else if (shouldDeleteExistingValue && !shouldDeleteExistingValue(key, existingValue, valueInNewMap)) { - map.delete(key); + onExistingValue(key, existingValue, valueInNewMap); } }); @@ -3549,7 +3542,7 @@ namespace ts { }); } else { - clearMap(map, options.onDeleteExistingValue); + clearMap(map, options.onDeleteValue); } } } diff --git a/src/server/project.ts b/src/server/project.ts index 9984788403b..2233ef1e941 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1231,7 +1231,7 @@ namespace ts.server { path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None ), // Close existing watch thats not needed any more - onDeleteExistingValue: (directory, watcher) => this.projectService.closeDirectoryWatcher( + onDeleteValue: (directory, watcher) => this.projectService.closeDirectoryWatcher( WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded ) } From 989508245b4dc0030f16981b038682fb60df0334 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 12:52:29 -0700 Subject: [PATCH 059/144] Updating according to feedback from PR --- src/compiler/builder.ts | 6 +++--- src/compiler/commandLineParser.ts | 10 ++++------ src/compiler/core.ts | 31 +++++++++---------------------- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 19 +++++++++++-------- src/compiler/watchedProgram.ts | 2 +- src/services/services.ts | 2 +- 7 files changed, 30 insertions(+), 42 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 849732f0afb..04d520cb228 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -60,7 +60,7 @@ namespace ts { function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) { outputFiles.push({ name: fileName, writeByteOrderMark, text }); if (isDetailed) { - emittedSourceFiles = concatenate(emittedSourceFiles, sourceFiles); + emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles); } } } @@ -139,7 +139,7 @@ namespace ts { function ensureProgramGraph(program: Program) { if (!emitHandler) { - createProgramGraph(program, noop); + createProgramGraph(program, returnFalse); } } @@ -457,7 +457,7 @@ namespace ts { } // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values()); + return arrayFrom(seenFileNamesMap.values()); } } } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 3748dd9b9a4..ac8f1ee8f97 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1975,15 +1975,13 @@ namespace ts { } /** - * 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. + * Gets the file names from the provided config file specs that contain, files, include, exclude and + * other properties needed to resolve the file names + * @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details * @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. + * @param extraFileExtensions optionaly file extra file extension information from host */ export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray = []): ExpandResult { basePath = normalizePath(basePath); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c163de24466..751177c4ef8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -459,14 +459,12 @@ namespace ts { return result; } - export function flatMapIter(iter: Iterator): T[]; - export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[]; - export function flatMapIter(iter: Iterator, mapfn?: (x: any) => any): any[] { - const result = []; + export function flatMapIter(iter: Iterator, mapfn: (x: T) => U | U[] | undefined): U[] { + const result: U[] = []; while (true) { const { value, done } = iter.next(); if (done) break; - const res = mapfn ? mapfn(value) : value; + const res = mapfn(value); if (res) { if (isArray(res)) { result.push(...res); @@ -1221,7 +1219,10 @@ namespace ts { } /** Does nothing. */ - export function noop(): any {} + export function noop(): void { } + + /** Do nothing and return false */ + export function returnFalse(): false { return false; } /** Throws an error because a function is not implemented. */ export function notImplemented(): never { @@ -2626,25 +2627,11 @@ namespace ts { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } - export interface HostForCaching { + export interface HostForCaching extends PartialSystem { useCaseSensitiveFileNames: boolean; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } - export interface CachedHost { - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + export interface CachedHost extends PartialSystem { addOrDeleteFileOrFolder(fileOrFolder: string): void; clearCache(): void; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c642d99ff9d..2dd3e465268 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -585,7 +585,7 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; - const hasInvalidatedResolution = host.hasInvalidatedResolution || noop; + const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 193997a8a86..aafb97f4131 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,14 +30,23 @@ namespace ts { mtime?: Date; } - export interface System { + export interface PartialSystem { + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + } + + export interface System extends PartialSystem { args: string[]; newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -45,13 +54,7 @@ namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; getExecutingFilePath(): string; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index ca0292fa533..5229e11ea97 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -630,7 +630,7 @@ namespace ts { // Reload is pending, do the reload if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host, /*extraFileExtension*/ []); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); if (!configFileSpecs.filesSpecs) { reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } diff --git a/src/services/services.ts b/src/services/services.ts index ba1c48b9037..b06f92344d3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1116,7 +1116,7 @@ namespace ts { let hostCache = new HostCache(host, getCanonicalFileName); const rootFileNames = hostCache.getRootFileNames(); - const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || noop; + const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; // If the program is already up-to-date, we can reuse it if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution)) { From f1b1b12604dad4af982a1d76181dcb20bef1cb73 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 14:59:51 -0700 Subject: [PATCH 060/144] More work based on feedback --- src/compiler/core.ts | 118 ++++++++++++++++++++++---------- src/compiler/resolutionCache.ts | 4 +- src/compiler/watchedProgram.ts | 67 +++++++++--------- src/server/editorServices.ts | 12 ++-- src/server/lsHost.ts | 26 ++++--- src/server/project.ts | 20 +++--- src/server/scriptInfo.ts | 2 +- 7 files changed, 147 insertions(+), 102 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 751177c4ef8..1eab3582cb9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2631,12 +2631,16 @@ namespace ts { useCaseSensitiveFileNames: boolean; } - export interface CachedHost extends PartialSystem { - addOrDeleteFileOrFolder(fileOrFolder: string): void; + export interface CachedHost { + addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; + addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; clearCache(): void; } - export function createCachedHost(host: HostForCaching): CachedHost { + export interface CachedPartialSystem extends PartialSystem, CachedHost { + } + + export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); @@ -2649,6 +2653,7 @@ namespace ts { getDirectories, readDirectory, addOrDeleteFileOrFolder, + addOrDeleteFile, clearCache }; @@ -2656,33 +2661,46 @@ namespace ts { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } - function getFileSystemEntries(rootDir: string) { - const path = toPath(rootDir); - const cachedResult = cachedReadDirectoryResult.get(path); - if (cachedResult) { - return cachedResult; - } + function getCachedFileSystemEntries(rootDirPath: Path): FileSystemEntries | undefined { + return cachedReadDirectoryResult.get(rootDirPath); + } + function getCachedFileSystemEntriesForBaseDir(path: Path): FileSystemEntries | undefined { + return getCachedFileSystemEntries(getDirectoryPath(path)); + } + + function getBaseNameOfFileName(fileName: string) { + return getBaseFileName(normalizePath(fileName)); + } + + function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { const resultFromHost: FileSystemEntries = { files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], directories: host.getDirectories(rootDir) || [] }; - cachedReadDirectoryResult.set(path, resultFromHost); + cachedReadDirectoryResult.set(rootDirPath, resultFromHost); return resultFromHost; } - function canWorkWithCacheForDir(rootDir: string) { - // Some of the hosts might not be able to handle read directory or getDirectories - const path = toPath(rootDir); - if (cachedReadDirectoryResult.get(path)) { - return true; + /** + * If the readDirectory result was already cached, it returns that + * Otherwise gets result from host and caches it. + * The host request is done under try catch block to avoid caching incorrect result + */ + function tryReadDirectory(rootDir: string, rootDirPath: Path): FileSystemEntries | undefined { + const cachedResult = getCachedFileSystemEntries(rootDirPath); + if (cachedResult) { + return cachedResult; } + try { - return getFileSystemEntries(rootDir); + return createCachedFileSystemEntries(rootDir, rootDirPath); } catch (_e) { - return false; + // If there is exception to read directories, dont cache the result and direct the calls to host + Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); + return undefined; } } @@ -2708,19 +2726,18 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { const path = toPath(fileName); - const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); - const baseFileName = getBaseFileName(normalizePath(fileName)); + const result = getCachedFileSystemEntriesForBaseDir(path); if (result) { - result.files = updateFileSystemEntry(result.files, baseFileName, /*isValid*/ true); + updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); } return host.writeFile(fileName, data, writeByteOrderMark); } function fileExists(fileName: string): boolean { const path = toPath(fileName); - const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); - const baseName = getBaseFileName(normalizePath(fileName)); - return (result && hasEntry(result.files, baseName)) || host.fileExists(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + return (result && hasEntry(result.files, getBaseNameOfFileName(fileName))) || + host.fileExists(fileName); } function directoryExists(dirPath: string): boolean { @@ -2730,8 +2747,8 @@ namespace ts { function createDirectory(dirPath: string) { const path = toPath(dirPath); - const result = cachedReadDirectoryResult.get(getDirectoryPath(path)); - const baseFileName = getBaseFileName(path); + const result = getCachedFileSystemEntriesForBaseDir(path); + const baseFileName = getBaseNameOfFileName(dirPath); if (result) { result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); } @@ -2739,41 +2756,68 @@ namespace ts { } function getDirectories(rootDir: string): string[] { - if (canWorkWithCacheForDir(rootDir)) { - return getFileSystemEntries(rootDir).directories.slice(); + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return result.directories.slice(); } return host.getDirectories(rootDir); } + function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { - if (canWorkWithCacheForDir(rootDir)) { - return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, path => getFileSystemEntries(path)); + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries); } return host.readDirectory(rootDir, extensions, excludes, includes, depth); + + function getFileSystemEntries(dir: string) { + const path = toPath(dir); + if (path === rootDirPath) { + return result; + } + return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + } } - function addOrDeleteFileOrFolder(fileOrFolder: string) { - const path = toPath(fileOrFolder); - const existingResult = cachedReadDirectoryResult.get(path); + function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrFolderPath); if (existingResult) { // This was a folder already present, remove it if this doesnt exist any more if (!host.directoryExists(fileOrFolder)) { - cachedReadDirectoryResult.delete(path); + cachedReadDirectoryResult.delete(fileOrFolderPath); } } else { // This was earlier a file (hence not in cached directory contents) // or we never cached the directory containing it - const parentResult = cachedReadDirectoryResult.get(getDirectoryPath(path)); + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath); if (parentResult) { - const baseName = getBaseFileName(fileOrFolder); + const baseName = getBaseNameOfFileName(fileOrFolder); if (parentResult) { - parentResult.files = updateFileSystemEntry(parentResult.files, baseName, host.fileExists(path)); - parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(path)); + updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); + parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); } } } } + function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { + if (eventKind === FileWatcherEventKind.Changed) { + return; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); + if (parentResult) { + updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); + } + } + + function updateFilesOfFileSystemEntry(parentResult: FileSystemEntries, baseName: string, fileExists: boolean) { + parentResult.files = updateFileSystemEntry(parentResult.files, baseName, fileExists); + } + function clearCache() { cachedReadDirectoryResult.clear(); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 615340e4872..0379289cc8e 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -40,7 +40,7 @@ namespace ts { export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - watchForFailedLookupLocation: (failedLookupLocation: string, containingFile: string, name: string) => FileWatcher, + watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher, log: (s: string) => void, resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache { @@ -201,7 +201,7 @@ namespace ts { } else { log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, containingFile, name); + const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name); failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 }); } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 5229e11ea97..e79049b983c 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -520,7 +520,7 @@ namespace ts { function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - updateCachedSystem(fileName, path); + updateCachedSystemWithFile(fileName, path, eventKind); const hostSourceFile = sourceFilesCache.get(path); if (hostSourceFile) { // Update the cache @@ -547,21 +547,19 @@ namespace ts { scheduleProgramUpdate(); } - function updateCachedSystem(fileName: string, path: Path) { + function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(fileName, getDirectoryPath(path)); - (host as CachedSystem).addOrDeleteFileOrFolder(normalizePath(absoluteNormalizedPath)); + (host as CachedSystem).addOrDeleteFile(fileName, path, eventKind); } } - function watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) { - return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, containingFile, name)); + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, failedLookupLocationPath, containingFile, name)); } - function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, containingFile: string, name: string) { + function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); - const path = toPath(failedLookupLocation); - updateCachedSystem(failedLookupLocation, path); + updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); scheduleProgramUpdate(); } @@ -574,14 +572,14 @@ namespace ts { fileWatcher.close(); } - function onMissingFileChange(filename: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { - writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${filename}`); + function onMissingFileChange(fileName: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { + writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + updateCachedSystemWithFile(fileName, missingFilePath, eventKind); + if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); missingFilesMap.delete(missingFilePath); - updateCachedSystem(filename, missingFilePath); - // Delete the entry in the source files cache so that new source file is created removeSourceFile(missingFilePath); @@ -600,8 +598,8 @@ namespace ts { } function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) { - return host.watchDirectory(directory, fileName => - onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileName, directory)), + return host.watchDirectory(directory, fileOrFolder => + onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileOrFolder, directory)), (flags & WatchDirectoryFlags.Recursive) !== 0); } @@ -609,24 +607,24 @@ namespace ts { watcher.close(); } - function onFileAddOrRemoveInWatchedDirectory(fileName: string) { + function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) { Debug.assert(!!configFileName); - const path = toPath(fileName); + const fileOrFolderPath = toPath(fileOrFolder); // Since the file existance changed, update the sourceFiles cache - updateCachedSystem(fileName, path); - removeSourceFile(path); + (host as CachedSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + removeSourceFile(fileOrFolderPath); // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. - if (fileName && !isSupportedSourceFileName(fileName, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileName}`); + if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); return; } - writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); + writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileOrFolder}`); // Reload is pending, do the reload if (!needsReload) { @@ -658,9 +656,7 @@ namespace ts { } } - interface CachedSystem extends System { - addOrDeleteFileOrFolder(fileOrFolder: string): void; - clearCache(): void; + interface CachedSystem extends System, CachedHost { } function createCachedSystem(host: System): CachedSystem { @@ -675,7 +671,7 @@ namespace ts { const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; - const cachedHost = createCachedHost(host); + const cachedPartialSystem = createCachedPartialSystem(host); return { args: host.args, newLine: host.newLine, @@ -683,17 +679,17 @@ namespace ts { write: s => host.write(s), readFile: (path, encoding?) => host.readFile(path, encoding), getFileSize, - writeFile: (fileName, data, writeByteOrderMark?) => cachedHost.writeFile(fileName, data, writeByteOrderMark), + writeFile: (fileName, data, writeByteOrderMark?) => cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark), watchFile, watchDirectory, resolvePath: path => host.resolvePath(path), - fileExists: fileName => cachedHost.fileExists(fileName), - directoryExists: dir => cachedHost.directoryExists(dir), - createDirectory: dir => cachedHost.createDirectory(dir), + fileExists: fileName => cachedPartialSystem.fileExists(fileName), + directoryExists: dir => cachedPartialSystem.directoryExists(dir), + createDirectory: dir => cachedPartialSystem.createDirectory(dir), getExecutingFilePath: () => host.getExecutingFilePath(), - getCurrentDirectory: () => cachedHost.getCurrentDirectory(), - getDirectories: dir => cachedHost.getDirectories(dir), - readDirectory: (path, extensions, excludes, includes, depth) => cachedHost.readDirectory(path, extensions, excludes, includes, depth), + getCurrentDirectory: () => cachedPartialSystem.getCurrentDirectory(), + getDirectories: dir => cachedPartialSystem.getDirectories(dir), + readDirectory: (path, extensions, excludes, includes, depth) => cachedPartialSystem.readDirectory(path, extensions, excludes, includes, depth), getModifiedTime, createHash, getMemoryUsage, @@ -704,8 +700,9 @@ namespace ts { debugMode: host.debugMode, setTimeout, clearTimeout, - addOrDeleteFileOrFolder: fileOrFolder => cachedHost.addOrDeleteFileOrFolder(fileOrFolder), - clearCache: () => cachedHost.clearCache() + addOrDeleteFileOrFolder: (fileOrFolder, fileOrFolderPath) => cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath), + addOrDeleteFile: (file, filePath, eventKind) => cachedPartialSystem.addOrDeleteFile(file, filePath, eventKind), + clearCache: () => cachedPartialSystem.clearCache() }; } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8507b6f5cd2..0b7ba9e7441 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -695,8 +695,8 @@ namespace ts.server { } /* @internal */ - onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); + onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: NormalizedPath) { + project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); project.updateTypes(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -707,15 +707,15 @@ namespace ts.server { * @param fileName the absolute file name that changed in watched directory */ /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: NormalizedPath) { + project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); const configFilename = project.getConfigFilePath(); // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. - if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { - this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileName}`); + if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); return; } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index f4cbd402604..44b9893bc3d 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -4,12 +4,13 @@ /// namespace ts.server { + /*@internal*/ export class CachedServerHost implements ServerHost { args: string[]; newLine: string; useCaseSensitiveFileNames: boolean; - private readonly cachedHost: CachedHost; + private readonly cachedPartialSystem: CachedPartialSystem; readonly trace: (s: string) => void; readonly realpath?: (path: string) => string; @@ -24,7 +25,7 @@ namespace ts.server { if (this.host.realpath) { this.realpath = path => this.host.realpath(path); } - this.cachedHost = createCachedHost(host); + this.cachedPartialSystem = createCachedPartialSystem(host); } write(s: string) { @@ -32,7 +33,7 @@ namespace ts.server { } writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - this.cachedHost.writeFile(fileName, data, writeByteOrderMark); + this.cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark); } resolvePath(path: string) { @@ -48,7 +49,7 @@ namespace ts.server { } getCurrentDirectory() { - return this.cachedHost.getCurrentDirectory(); + return this.cachedPartialSystem.getCurrentDirectory(); } exit(exitCode?: number) { @@ -61,32 +62,35 @@ namespace ts.server { } getDirectories(rootDir: string) { - return this.cachedHost.getDirectories(rootDir); + return this.cachedPartialSystem.getDirectories(rootDir); } readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return this.cachedHost.readDirectory(rootDir, extensions, excludes, includes, depth); + return this.cachedPartialSystem.readDirectory(rootDir, extensions, excludes, includes, depth); } fileExists(fileName: string): boolean { - return this.cachedHost.fileExists(fileName); + return this.cachedPartialSystem.fileExists(fileName); } directoryExists(dirPath: string) { - return this.cachedHost.directoryExists(dirPath); + return this.cachedPartialSystem.directoryExists(dirPath); } readFile(path: string, encoding?: string): string { return this.host.readFile(path, encoding); } + addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath, fileOrFolderPath: Path) { + return this.cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } - addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath) { - return this.cachedHost.addOrDeleteFileOrFolder(fileOrFolder); + addOrDeleteFile(file: string, path: Path, eventKind: FileWatcherEventKind) { + return this.cachedPartialSystem.addOrDeleteFile(file, path, eventKind); } clearCache() { - return this.cachedHost.clearCache(); + return this.cachedPartialSystem.clearCache(); } setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { diff --git a/src/server/project.ts b/src/server/project.ts index 2233ef1e941..552778e86c0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -218,7 +218,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - (failedLookupLocation, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, containingFile, name), + (failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name), s => this.projectService.logger.info(s), (primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host, this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName()) @@ -235,12 +235,12 @@ namespace ts.server { this.markAsDirty(); } - private watchFailedLookupLocation(failedLookupLocation: string, containingFile: string, name: string) { + private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { // There is some kind of change in the failed lookup location, update the program - return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (__fileName, eventKind) => { + return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (fileName, eventKind) => { this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(failedLookupLocation)); + (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); } this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); this.markAsDirty(); @@ -719,16 +719,15 @@ namespace ts.server { private addMissingFileWatcher(missingFilePath: Path) { const fileWatcher = this.projectService.addFileWatcher( WatchType.MissingFilePath, this, missingFilePath, - (filename, eventKind) => { + (fileName, eventKind) => { + if (this.projectKind === ProjectKind.Configured) { + (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + } + if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { this.missingFilesMap.delete(missingFilePath); this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); - if (this.projectKind === ProjectKind.Configured) { - const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath)); - (this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath)); - } - // When a missing file is created, we should update the graph. this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); @@ -1076,6 +1075,7 @@ namespace ts.server { return super.updateGraph(); } + /*@internal*/ getCachedServerHost() { return this.lsHost.host as CachedServerHost; } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 6d439e92385..2dcbc52d87d 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -237,7 +237,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(this.fileName); + (p.lsHost.host as CachedServerHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways From 136b091a4a71065db75c972adeb810bb1a2b812f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 15:52:20 -0700 Subject: [PATCH 061/144] Update based on feedback --- src/compiler/program.ts | 35 ++++++++++--------- src/compiler/resolutionCache.ts | 23 +++++++++---- src/compiler/types.ts | 3 +- src/compiler/utilities.ts | 9 +++-- src/compiler/watchedProgram.ts | 9 +++-- src/harness/unittests/programMissingFiles.ts | 36 ++++++++++++-------- src/server/project.ts | 5 --- 7 files changed, 69 insertions(+), 51 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2dd3e465268..04af5c4d972 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -393,7 +393,7 @@ namespace ts { allDiagnostics?: Diagnostic[]; } - export function isProgramUptoDate(program: Program, rootFileNames: string[], newOptions: CompilerOptions, + export function isProgramUptoDate(program: Program | undefined, rootFileNames: string[], newOptions: CompilerOptions, getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean { // If we haven't create a program yet, then it is not up-to-date if (!program) { @@ -406,14 +406,12 @@ namespace ts { } // If any file is not up-to-date, then the whole program is not up-to-date - for (const file of program.getSourceFiles()) { - if (!sourceFileUpToDate(program.getSourceFile(file.fileName))) { + if (program.getSourceFiles().some(sourceFileNotUptoDate)) { return false; - } } // If any of the missing file paths are now created - if (program.getMissingFilePaths().some(missingFilePath => fileExists(missingFilePath))) { + if (program.getMissingFilePaths().some(fileExists)) { return false; } @@ -431,15 +429,18 @@ namespace ts { return true; - function sourceFileUpToDate(sourceFile: SourceFile): boolean { - return sourceFile && - sourceFile.version === getSourceVersion(sourceFile.path) && - !hasInvalidatedResolution(sourceFile.path); + function sourceFileNotUptoDate(sourceFile: SourceFile): boolean { + return sourceFile.version !== getSourceVersion(sourceFile.path) || + hasInvalidatedResolution(sourceFile.path); } } + /** + * Determined if source file needs to be re-created even if its text hasnt changed + */ function shouldProgramCreateNewSourceFiles(program: Program, newOptions: CompilerOptions) { // If any of these options change, we cant reuse old source file even if version match + // The change in options like these could result in change in syntax tree change const oldOptions = program && program.getCompilerOptions(); return oldOptions && (oldOptions.target !== newOptions.target || @@ -478,19 +479,19 @@ namespace ts { ); } - export interface WildcardDirectoryWatchers { + export interface WildcardDirectoryWatcher { watcher: FileWatcher; flags: WatchDirectoryFlags; } /** - * Updates the existing wild card directory watcyhes with the new set of wild card directories from the config file after new program is created + * Updates the existing wild card directory watches with the new set of wild card directories from the config file after new program is created */ export function updateWatchingWildcardDirectories( - existingWatchedForWildcards: Map, + existingWatchedForWildcards: Map, wildcardDirectories: Map, watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher, - closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatchers, flagsChanged: boolean) => void + closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flagsChanged: boolean) => void ) { mutateMap( existingWatchedForWildcards, @@ -506,7 +507,7 @@ namespace ts { } ); - function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatchers { + function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { // Create new watch and recursive info return { watcher: watchDirectory(directory, flags), @@ -514,7 +515,7 @@ namespace ts { }; } - function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatchers, flags: WatchDirectoryFlags) { + function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags) { // Watcher needs to be updated if the recursive flags dont match if (wildcardDirectoryWatcher.flags === flags) { return; @@ -622,7 +623,7 @@ namespace ts { let redirectTargetsSet = createMap(); const filesByName = createMap(); - let missingFilePaths: Path[]; + let missingFilePaths: ReadonlyArray; // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; @@ -666,6 +667,8 @@ namespace ts { missingFilePaths = arrayFrom(filesByName.keys(), p => p).filter(p => !filesByName.get(p)); } + Debug.assert(!!missingFilePaths); + // unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks moduleResolutionCache = undefined; diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 0379289cc8e..dc2e61cd578 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,7 +1,9 @@ /// /// +/*@internal*/ namespace ts { + /** This is the cache of module/typedirectives resolution that can be retained across program */ export interface ResolutionCache { setModuleResolutionHost(host: ModuleResolutionHost): void; @@ -19,10 +21,15 @@ namespace ts { clear(): void; } - type NameResolutionWithFailedLookupLocations = { failedLookupLocations: string[], isInvalidated?: boolean }; - type ResolverWithGlobalCache = (primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost) => ResolvedModuleWithFailedLookupLocations | undefined; + interface NameResolutionWithFailedLookupLocations { + readonly failedLookupLocations: string[]; + isInvalidated?: boolean; + } + + interface ResolverWithGlobalCache { + (primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations | undefined; + } - /*@internal*/ export function resolveWithGlobalCache(primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string | undefined, projectName: string): ResolvedModuleWithFailedLookupLocations | undefined { if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) { // otherwise try to load typings from @types @@ -36,7 +43,11 @@ namespace ts { } } - /*@internal*/ + interface FailedLookupLocationsWatcher { + fileWatcher: FileWatcher; + refCount: number; + } + export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, @@ -51,7 +62,6 @@ namespace ts { const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); - type FailedLookupLocationsWatcher = { fileWatcher: FileWatcher; refCount: number }; const failedLookupLocationsWatches = createMap(); return { @@ -71,11 +81,10 @@ namespace ts { } function clear() { - failedLookupLocationsWatches.forEach((failedLookupLocationWatcher, failedLookupLocationPath: Path) => { + clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => { log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`); failedLookupLocationWatcher.fileWatcher.close(); }); - failedLookupLocationsWatches.clear(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e4ea722df3d..9fe93f55cb3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2411,7 +2411,7 @@ namespace ts { * program source file but could not be located. */ /* @internal */ - getMissingFilePaths(): Path[]; + getMissingFilePaths(): ReadonlyArray; /** * Emits the JavaScript and declaration files. If targetSourceFile is not specified, then @@ -3561,6 +3561,7 @@ namespace ts { charset?: string; checkJs?: boolean; /* @internal */ configFilePath?: string; + /** configFile is set as non enumerable property so as to avoid checking of json source files */ /* @internal */ readonly configFile?: JsonSourceFile; declaration?: boolean; declarationDir?: string; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6e26a287b83..ebb6c785588 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -403,7 +403,10 @@ namespace ts { ((node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(node)); } - /* @internal */ + export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration { + return isModuleDeclaration(node) && node.name.kind === SyntaxKind.StringLiteral; + } + export function isNonGlobalAmbientModule(node: Node): node is ModuleDeclaration & { name: StringLiteral } { return isModuleDeclaration(node) && isStringLiteral(node.name); } @@ -1403,10 +1406,6 @@ namespace ts { return SpecialPropertyAssignmentKind.None; } - export function isModuleWithStringLiteralName(node: Node): node is ModuleDeclaration { - return isModuleDeclaration(node) && node.name.kind === SyntaxKind.StringLiteral; - } - export function getExternalModuleName(node: Node): Expression { if (node.kind === SyntaxKind.ImportDeclaration) { return (node).moduleSpecifier; diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index e79049b983c..6423b981896 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -241,12 +241,13 @@ namespace ts { let needsReload: boolean; // true if the config file changed and needs to reload it from the disk let missingFilesMap: Map; // Map of file watchers for the missing files let configFileWatcher: FileWatcher; // watcher for the config file - let watchedWildcardDirectories: Map; // map of watchers for the wild card directories in the config file + let watchedWildcardDirectories: Map; // map of watchers for the wild card directories in the config file let timerToUpdateProgram: any; // timer callback to recompile the program const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions + let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; @@ -291,9 +292,10 @@ namespace ts { // Create the compiler host const compilerHost = createWatchedCompilerHost(compilerOptions); resolutionCache.setModuleResolutionHost(compilerHost); - if (changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { + if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } + hasChangedCompilerOptions = false; beforeCompile(compilerOptions); // Compile the program @@ -504,6 +506,7 @@ namespace ts { const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; compilerOptions = configParseResult.options; + hasChangedCompilerOptions = true; configFileSpecs = configParseResult.configFileSpecs; configFileWildCardDirectories = configParseResult.wildcardDirectories; @@ -603,7 +606,7 @@ namespace ts { (flags & WatchDirectoryFlags.Recursive) !== 0); } - function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatchers, _recursiveChanged: boolean) { + function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatcher, _recursiveChanged: boolean) { watcher.close(); } diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 668b684a250..20323b37380 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -1,6 +1,18 @@ /// namespace ts { + function verifyMissingFilePaths(missingPaths: ReadonlyArray, expected: ReadonlyArray) { + assert.isDefined(missingPaths); + const map = arrayToMap(expected, k => k, _v => true); + for (const missing of missingPaths) { + const value = map.get(missing); + assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); + map.set(missing, false); + } + const notFound = mapDefinedIter(map.keys(), k => map.get(k) === true ? k : undefined); + assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); + } + describe("Program.getMissingFilePaths", () => { const options: CompilerOptions = { @@ -40,34 +52,31 @@ namespace ts { it("handles no missing root files", () => { const program = createProgram([emptyFileRelativePath], options, testCompilerHost); const missing = program.getMissingFilePaths(); - assert.isDefined(missing); - assert.deepEqual(missing, []); + verifyMissingFilePaths(missing, []); }); it("handles missing root file", () => { const program = createProgram(["./nonexistent.ts"], options, testCompilerHost); const missing = program.getMissingFilePaths(); - assert.isDefined(missing); - assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path }); it("handles multiple missing root files", () => { const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); - assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); }); it("handles a mix of present and missing root files", () => { const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); - assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]); }); it("handles repeatedly specified root files", () => { const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost); const missing = program.getMissingFilePaths(); - assert.isDefined(missing); - assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); + verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); }); it("normalizes file paths", () => { @@ -81,9 +90,8 @@ namespace ts { it("handles missing triple slash references", () => { const program = createProgram([referenceFileRelativePath], options, testCompilerHost); - const missing = program.getMissingFilePaths().sort(); - assert.isDefined(missing); - assert.deepEqual(missing, [ + const missing = program.getMissingFilePaths(); + verifyMissingFilePaths(missing, [ // From absolute reference "d:/imaginary/nonexistent1.ts", @@ -100,4 +108,4 @@ namespace ts { ]); }); }); -} \ No newline at end of file +} diff --git a/src/server/project.ts b/src/server/project.ts index 552778e86c0..d50621c50bb 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1020,11 +1020,6 @@ namespace ts.server { } } - interface WildcardDirectoryWatcher { - watcher: FileWatcher; - flags: WatchDirectoryFlags; - } - /** * If a file is opened, the server will look for a tsconfig (or jsconfig) * and if successfull create a ConfiguredProject for it. From 6bf9133461fd1f3f2cae1fbc02ee4a6319a000a6 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 15:52:20 -0700 Subject: [PATCH 062/144] Update to PR feedback --- src/compiler/resolutionCache.ts | 39 ++++++++++++++++----------------- src/server/project.ts | 4 ++-- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index dc2e61cd578..e50e32dfcef 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -26,23 +26,6 @@ namespace ts { isInvalidated?: boolean; } - interface ResolverWithGlobalCache { - (primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations | undefined; - } - - export function resolveWithGlobalCache(primaryResult: ResolvedModuleWithFailedLookupLocations, moduleName: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string | undefined, projectName: string): ResolvedModuleWithFailedLookupLocations | undefined { - if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) { - // otherwise try to load typings from @types - - // create different collection of failed lookup locations for second pass - // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); - if (resolvedModule) { - return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) }; - } - } - } - interface FailedLookupLocationsWatcher { fileWatcher: FileWatcher; refCount: number; @@ -53,7 +36,8 @@ namespace ts { getCompilerOptions: () => CompilerOptions, watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher, log: (s: string) => void, - resolveWithGlobalCache?: ResolverWithGlobalCache): ResolutionCache { + projectName?: string, + getGlobalCache?: () => string | undefined): ResolutionCache { let host: ModuleResolutionHost; let filesWithChangedSetOfUnresolvedImports: Path[]; @@ -107,9 +91,24 @@ namespace ts { function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); - // return result immediately only if it is .ts, .tsx or .d.ts + // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts + if (!getGlobalCache) { + return primaryResult; + } + // otherwise try to load typings from @types - return (resolveWithGlobalCache && resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host)) || primaryResult; + const globalCache = getGlobalCache(); + if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) { + // create different collection of failed lookup locations for second pass + // if it will fail and we've already found something during the first pass - we don't want to pollute its results + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); + if (resolvedModule) { + return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) }; + } + } + + // Default return the result from the first pass + return primaryResult; } function resolveNamesWithLocalCache( diff --git a/src/server/project.ts b/src/server/project.ts index d50621c50bb..fc6dca31ecd 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -220,8 +220,8 @@ namespace ts.server { () => this.compilerOptions, (failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name), s => this.projectService.logger.info(s), - (primaryResult, moduleName, compilerOptions, host) => resolveWithGlobalCache(primaryResult, moduleName, compilerOptions, host, - this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined, this.getProjectName()) + this.getProjectName(), + () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined ); this.lsHost.compilationSettings = this.compilerOptions; this.resolutionCache.setModuleResolutionHost(this.lsHost); From a99c04e8f9893f9f193fad81df6a7f4430a5a077 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 15:52:20 -0700 Subject: [PATCH 063/144] Make the failedLookuplocations to be readonly array --- src/compiler/resolutionCache.ts | 36 ++++++++++++++++++--------------- src/compiler/types.ts | 4 ++-- src/compiler/watchedProgram.ts | 5 +++-- src/server/project.ts | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e50e32dfcef..4fefd87a834 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -13,7 +13,7 @@ namespace ts { resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - invalidateResolutionOfDeletedFile(filePath: Path): void; + invalidateResolutionOfFile(filePath: Path): void; invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void; createHasInvalidatedResolution(): HasInvalidatedResolution; @@ -22,7 +22,7 @@ namespace ts { } interface NameResolutionWithFailedLookupLocations { - readonly failedLookupLocations: string[]; + readonly failedLookupLocations: ReadonlyArray; isInvalidated?: boolean; } @@ -40,9 +40,12 @@ namespace ts { getGlobalCache?: () => string | undefined): ResolutionCache { let host: ModuleResolutionHost; - let filesWithChangedSetOfUnresolvedImports: Path[]; - let filesWithInvalidatedResolutions: Map; + let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; + let filesWithInvalidatedResolutions: Map | undefined; + // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. + // The key in the map is source file's path. + // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); @@ -54,7 +57,7 @@ namespace ts { finishRecordingFilesWithChangedResolutions, resolveModuleNames, resolveTypeReferenceDirectives, - invalidateResolutionOfDeletedFile, + invalidateResolutionOfFile, invalidateResolutionOfChangedFailedLookupLocation, createHasInvalidatedResolution, clear @@ -65,6 +68,7 @@ namespace ts { } function clear() { + // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => { log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`); failedLookupLocationWatcher.fileWatcher.close(); @@ -103,7 +107,7 @@ namespace ts { // if it will fail and we've already found something during the first pass - we don't want to pollute its results const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); if (resolvedModule) { - return { resolvedModule, failedLookupLocations: primaryResult.failedLookupLocations.concat(failedLookupLocations) }; + return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as Array, failedLookupLocations) }; } } @@ -229,20 +233,20 @@ namespace ts { } type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void; - function withFailedLookupLocations(failedLookupLocations: string[], containingFile: string, name: string, fn: FailedLookupLocationAction) { + function withFailedLookupLocations(failedLookupLocations: ReadonlyArray, containingFile: string, name: string, fn: FailedLookupLocationAction) { forEach(failedLookupLocations, failedLookupLocation => { fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name); }); } - function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: string[], failedLookupLocations: string[]) { + function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray | undefined, failedLookupLocations: ReadonlyArray) { if (failedLookupLocations) { if (existingFailedLookupLocations) { - const existingWatches = arrayToMap(existingFailedLookupLocations, failedLookupLocation => toPath(failedLookupLocation)); + const existingWatches = arrayToMap(existingFailedLookupLocations, toPath); for (const failedLookupLocation of failedLookupLocations) { const failedLookupLocationPath = toPath(failedLookupLocation); if (existingWatches && existingWatches.has(failedLookupLocationPath)) { - // still has same failed lookup location, keep the was + // still has same failed lookup location, keep the watch existingWatches.delete(failedLookupLocationPath); } else { @@ -272,7 +276,7 @@ namespace ts { cache: Map>, getResult: (s: T) => R, getResultFileName: (result: R) => string | undefined) { - cache.forEach((value, path: Path) => { + cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); value.forEach((resolution, name) => { @@ -280,7 +284,7 @@ namespace ts { }); } else if (value) { - value.forEach((resolution, __name) => { + value.forEach(resolution => { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { @@ -298,10 +302,10 @@ namespace ts { function invalidateResolutionCacheOfChangedFailedLookupLocation( failedLookupLocation: string, cache: Map>) { - cache.forEach((value, containingFile: Path) => { + cache.forEach((value, containingFile) => { if (value) { - value.forEach((resolution, __name) => { - if (resolution && !resolution.isInvalidated && contains(resolution.failedLookupLocations, failedLookupLocation)) { + value.forEach(resolution => { + if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocation)) { // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); @@ -311,7 +315,7 @@ namespace ts { }); } - function invalidateResolutionOfDeletedFile(filePath: Path) { + function invalidateResolutionOfFile(filePath: Path) { invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9fe93f55cb3..87d0ad153c3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4011,7 +4011,7 @@ namespace ts { export interface ResolvedModuleWithFailedLookupLocations { readonly resolvedModule: ResolvedModuleFull | undefined; /* @internal */ - readonly failedLookupLocations: string[]; + readonly failedLookupLocations: ReadonlyArray; /*@internal*/ isInvalidated?: boolean; } @@ -4025,7 +4025,7 @@ namespace ts { export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; - readonly failedLookupLocations: string[]; + readonly failedLookupLocations: ReadonlyArray; /*@internal*/ isInvalidated?: boolean; } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 6423b981896..bd2473a699d 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -437,7 +437,7 @@ namespace ts { if (hostSourceFile !== undefined) { if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); - resolutionCache.invalidateResolutionOfDeletedFile(path); + resolutionCache.invalidateResolutionOfFile(path); } sourceFilesCache.delete(path); } @@ -461,6 +461,7 @@ namespace ts { } else if (hostSourceFileInfo.sourceFile === oldSourceFile) { sourceFilesCache.delete(oldSourceFile.path); + resolutionCache.invalidateResolutionOfFile(oldSourceFile.path); } } } @@ -528,7 +529,7 @@ namespace ts { if (hostSourceFile) { // Update the cache if (eventKind === FileWatcherEventKind.Deleted) { - resolutionCache.invalidateResolutionOfDeletedFile(path); + resolutionCache.invalidateResolutionOfFile(path); if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); sourceFilesCache.set(path, (hostSourceFile.version++).toString()); diff --git a/src/server/project.ts b/src/server/project.ts index fc6dca31ecd..c5ee3aa42bb 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -547,7 +547,7 @@ namespace ts.server { if (this.isRoot(info)) { this.removeRoot(info); } - this.resolutionCache.invalidateResolutionOfDeletedFile(info.path); + this.resolutionCache.invalidateResolutionOfFile(info.path); this.cachedUnresolvedImportsPerFile.remove(info.path); if (detachFromProject) { From b66b7525617ecb1bd5b70944224e6b80db7bf772 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 11:15:42 -0700 Subject: [PATCH 064/144] Update based on feedback --- src/compiler/core.ts | 2 +- src/compiler/sys.ts | 3 +++ src/harness/unittests/programMissingFiles.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1eab3582cb9..e1193d7f71b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2736,7 +2736,7 @@ namespace ts { function fileExists(fileName: string): boolean { const path = toPath(fileName); const result = getCachedFileSystemEntriesForBaseDir(path); - return (result && hasEntry(result.files, getBaseNameOfFileName(fileName))) || + return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || host.fileExists(fileName); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index aafb97f4131..982d6793819 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,6 +30,9 @@ namespace ts { mtime?: Date; } + /** + * Partial interface of the System thats needed to support the caching of directory structure + */ export interface PartialSystem { writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 20323b37380..ce8abc232e7 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -3,7 +3,7 @@ namespace ts { function verifyMissingFilePaths(missingPaths: ReadonlyArray, expected: ReadonlyArray) { assert.isDefined(missingPaths); - const map = arrayToMap(expected, k => k, _v => true); + const map = arrayToSet(expected) as Map; for (const missing of missingPaths) { const value = map.get(missing); assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); From da0d374b303d1dc517767508922b765a46700c20 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 11:55:47 -0700 Subject: [PATCH 065/144] Made updates to not expose methods/types that arent needed. --- src/compiler/commandLineParser.ts | 11 +++++---- src/server/project.ts | 37 +++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d09e4cab6d2..cc3047b6d34 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1479,6 +1479,7 @@ namespace ts { } } + /*@internal*/ export function isErrorNoInputFiles(error: Diagnostic) { return error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code; } @@ -1975,15 +1976,13 @@ namespace ts { } /** - * 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. + * Gets the file names from the provided config file specs that contain, files, include, exclude and + * other properties needed to resolve the file names + * @param spec The config file specs extracted with file names to include, wildcards to include/exclude and other details * @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. + * @param extraFileExtensions optionaly file extra file extension information from host */ export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: ReadonlyArray): ExpandResult { basePath = normalizePath(basePath); diff --git a/src/server/project.ts b/src/server/project.ts index dde5d8070e4..88a08bfc863 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -158,8 +158,6 @@ namespace ts.server { private typingFiles: SortedReadonlyArray; - protected projectErrors: Diagnostic[]; - public typesVersion = 0; public isNonTsProject() { @@ -233,12 +231,12 @@ namespace ts.server { /** * Get the errors that dont have any file name associated */ - getGlobalProjectErrors() { - return filter(this.projectErrors, diagnostic => !diagnostic.file); + getGlobalProjectErrors(): ReadonlyArray { + return emptyArray; } - getAllProjectErrors() { - return this.projectErrors; + getAllProjectErrors(): ReadonlyArray { + return emptyArray; } getLanguageService(ensureSynchronized = true): LanguageService { @@ -323,7 +321,6 @@ namespace ts.server { this.program = undefined; this.builder = undefined; this.cachedUnresolvedImportsPerFile = undefined; - this.projectErrors = undefined; this.lsHost.dispose(); this.lsHost = undefined; @@ -371,6 +368,7 @@ namespace ts.server { return result; } + /*@internal*/ getRootFilesMap() { return this.rootFilesMap; } @@ -496,7 +494,7 @@ namespace ts.server { this.markAsDirty(); } - // add a root file to project + // add a root file that doesnt exist on host addMissingFileRoot(fileName: NormalizedPath) { const path = this.projectService.toPath(fileName); this.rootFilesMap.set(path, fileName); @@ -1004,6 +1002,8 @@ namespace ts.server { /** Used for configured projects which may have multiple open roots */ openRefCount = 0; + private projectErrors: Diagnostic[]; + constructor(configFileName: NormalizedPath, projectService: ProjectService, documentRegistry: DocumentRegistry, @@ -1018,7 +1018,7 @@ namespace ts.server { } /** - * Checks if the project has reload from disk pending, if thats pending, it reloads (and then updates graph as part of that) instead of just updating the graph + * If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph * @returns: true if set of files in the project stays the same and false - otherwise. */ updateGraph(): boolean { @@ -1030,6 +1030,7 @@ namespace ts.server { return super.updateGraph(); } + /*@internal*/ getCachedServerHost() { return this.lsHost.host as CachedServerHost; } @@ -1119,6 +1120,20 @@ namespace ts.server { return getDirectoryPath(this.getConfigFilePath()); } + /** + * Get the errors that dont have any file name associated + */ + getGlobalProjectErrors(): ReadonlyArray { + return filter(this.projectErrors, diagnostic => !diagnostic.file); + } + + /** + * Get all the project errors + */ + getAllProjectErrors(): ReadonlyArray { + return this.projectErrors; + } + setProjectErrors(projectErrors: Diagnostic[]) { this.projectErrors = projectErrors; } @@ -1143,6 +1158,7 @@ namespace ts.server { })); } + /*@internal*/ watchWildcards(wildcardDirectories: Map) { mutateMap( this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), @@ -1175,6 +1191,7 @@ namespace ts.server { this.projectService.closeDirectoryWatcher(WatchType.WildcardDirectories, this, directory, watcher, flags, closeReason); } + /*@internal*/ stopWatchingWildCards(reason: WatcherCloseReason) { if (this.directoriesWatchedForWildcards) { clearMap( @@ -1185,6 +1202,7 @@ namespace ts.server { } } + /*@internal*/ watchTypeRoots() { const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir)); mutateMap( @@ -1203,6 +1221,7 @@ namespace ts.server { ); } + /*@internal*/ stopWatchingTypeRoots(reason: WatcherCloseReason) { if (this.typeRootsWatchers) { clearMap( From 8deef58fd67877d6a5deccbf0f5530d7402c2856 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 12:15:03 -0700 Subject: [PATCH 066/144] Remove the unused function from the Project since builder has this logic now. --- src/compiler/program.ts | 5 +++- src/server/project.ts | 51 ----------------------------------------- 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 04af5c4d972..c3b83e1ba7c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -485,7 +485,10 @@ namespace ts { } /** - * Updates the existing wild card directory watches with the new set of wild card directories from the config file after new program is created + * Updates the existing wild card directory watches with the new set of wild card directories from the config file + * after new program is created because the config file was reloaded or program was created first time from the config file + * Note that there is no need to call this function when the program is updated with additional files without reloading config files, + * as wildcard directories wont change unless reloading config file */ export function updateWatchingWildcardDirectories( existingWatchedForWildcards: Map, diff --git a/src/server/project.ts b/src/server/project.ts index 2243dfc2b1d..a2141d64035 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -860,57 +860,6 @@ namespace ts.server { } } - getReferencedFiles(path: Path): Map { - const referencedFiles = createMap(); - if (!this.languageServiceEnabled) { - return referencedFiles; - } - - const sourceFile = this.getSourceFile(path); - if (!sourceFile) { - return referencedFiles; - } - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = this.program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const symbol = checker.getSymbolAtLocation(importName); - if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = symbol.declarations[0].getSourceFile(); - if (declarationSourceFile) { - referencedFiles.set(declarationSourceFile.path, true); - } - } - } - } - - const currentDirectory = getDirectoryPath(path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = this.projectService.toPath(referencedFile.fileName, currentDirectory); - referencedFiles.set(referencedPath, true); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = this.projectService.toPath(fileName, currentDirectory); - referencedFiles.set(typeFilePath, true); - }); - } - - return referencedFiles; - } - // remove a root file from project protected removeRoot(info: ScriptInfo): void { orderedRemoveItem(this.rootFiles, info); From c4251280754dea435b60fd32d6928f446bea3e42 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 13:13:12 -0700 Subject: [PATCH 067/144] When getting default project from session, get it only if the script info is not orphan Since the closed script info's that are orphan arent removed immediately but on next open request, treat the orphan script infos as if they are not present in the session --- .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 43 ++++++++++++++++++- src/server/editorServices.ts | 24 +++++------ src/server/scriptInfo.ts | 4 ++ 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 19ec12d7c5b..74972d23d96 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -63,7 +63,7 @@ namespace ts { const projectService = new server.ProjectService(svcOpts); const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined); - const project = projectService.assignScriptInfoToInferredProject(rootScriptInfo); + const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); return { project, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 8abec41de41..20fc4cb8a58 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -202,7 +202,7 @@ namespace ts.projectSystem { typingsInstaller: undefined, byteLength: Utils.byteLength, hrtime: process.hrtime, - logger: nullLogger, + logger: opts.logger || nullLogger, canUseEvents: false }; @@ -2634,6 +2634,47 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); }); + + it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => { + let hasErrorMsg = false; + const { close, hasLevel, loggingEnabled, info, group, perftrc, getLogFileName } = nullLogger; + const logger: server.Logger = { + close, hasLevel, loggingEnabled, info, group, perftrc, getLogFileName, + err: () => { + hasErrorMsg = true; + } + }; + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createServerHost([f1, libFile, config]); + const session = createSession(host, { logger }); + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: f1.path + } + }); + session.executeCommandSeq({ + command: server.CommandNames.Close, + arguments: { + file: f1.path + } + }); + session.executeCommandSeq({ + command: server.CommandNames.Geterr, + arguments: { + delay: 0, + files: [f1.path] + } + }); + assert.isFalse(hasErrorMsg); + }); }); describe("Proper errors", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 02e039ae88d..9f24f1ffa3f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -564,7 +564,7 @@ namespace ts.server { this.ensureProjectStructuresUptoDate(); } const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); - return scriptInfo && scriptInfo.getDefaultProject(); + return scriptInfo && !scriptInfo.isOrphan() && scriptInfo.getDefaultProject(); } /** @@ -588,7 +588,7 @@ namespace ts.server { else { projectsToUpdate = []; for (const f of this.changedFiles) { - projectsToUpdate = projectsToUpdate.concat(f.containingProjects); + addRange(projectsToUpdate, f.containingProjects); } } this.changedFiles = undefined; @@ -789,8 +789,8 @@ namespace ts.server { } /*@internal*/ - assignScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) { - Debug.assert(info.containingProjects.length === 0); + assignOrphanScriptInfoToInferredProject(info: ScriptInfo, projectRootPath?: string) { + Debug.assert(info.isOrphan()); const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) || this.getOrCreateSingleInferredProjectIfEnabled() || @@ -823,7 +823,7 @@ namespace ts.server { } private addToListOfOpenFiles(info: ScriptInfo) { - Debug.assert(info.containingProjects.length !== 0); + Debug.assert(!info.isOrphan()); for (const p of info.containingProjects) { // file is the part of configured project, addref the project if (p.projectKind === ProjectKind.Configured) { @@ -885,8 +885,8 @@ namespace ts.server { // collect orphaned files and assign them to inferred project just like we treat open of a file for (const f of this.openFiles) { - if (f.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(f); + if (f.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(f); } } @@ -907,7 +907,7 @@ namespace ts.server { private deleteOrphanScriptInfoNotInAnyProject() { this.filenameToScriptInfo.forEach(info => { - if (!info.isScriptOpen() && info.containingProjects.length === 0) { + if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo); this.filenameToScriptInfo.delete(info.path); @@ -1809,8 +1809,8 @@ namespace ts.server { for (const info of this.openFiles) { // collect all orphaned script infos from open files - if (info.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(info); + if (info.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(info); } else { // Or remove the root of inferred project if is referenced in more than one projects @@ -1867,8 +1867,8 @@ namespace ts.server { // At this point if file is part of any any configured or external project, then it would be present in the containing projects // So if it still doesnt have any containing projects, it needs to be part of inferred project - if (info.containingProjects.length === 0) { - this.assignScriptInfoToInferredProject(info, projectRootPath); + if (info.isOrphan()) { + this.assignOrphanScriptInfoToInferredProject(info, projectRootPath); } this.addToListOfOpenFiles(info); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 6d439e92385..133879a2dce 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -328,6 +328,10 @@ namespace ts.server { } } + isOrphan() { + return this.containingProjects.length === 0; + } + /** * @param line 1 based index */ From 84b2e2303335d0583077b11f845da5b95046c157 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 21 Aug 2017 11:39:04 -0700 Subject: [PATCH 068/144] More PR feedback work --- src/server/editorServices.ts | 18 +++++++++++++----- src/server/project.ts | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f1477fceac8..817f9eee606 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1718,7 +1718,7 @@ namespace ts.server { */ reloadProjects() { this.logger.info("reload projects."); - this.reloadConfiguredsProjectForFiles(this.openFiles, /*delayReload*/ false); + this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false); this.refreshInferredProjects(); } @@ -1734,7 +1734,7 @@ namespace ts.server { } } ); - this.reloadConfiguredsProjectForFiles(openFiles, /*delayReload*/ true); + this.reloadConfiguredProjectForFiles(openFiles, /*delayReload*/ true); this.delayInferredProjectsRefresh(); } @@ -1744,7 +1744,7 @@ namespace ts.server { * or schedules it for reload depending on delayReload option * If the there is no existing project it just opens the configured project for the config file */ - private reloadConfiguredsProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) { + private reloadConfiguredProjectForFiles(openFiles: ReadonlyArray, delayReload: boolean) { const updatedProjects = createMap(); // try to reload config file for all open files for (const info of openFiles) { @@ -1778,11 +1778,19 @@ namespace ts.server { */ private removeRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { // If the script info is root of inferred project, it could only be first containing 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 + // 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. if (info.containingProjects.length > 1 && info.containingProjects[0].projectKind === ProjectKind.Inferred && info.containingProjects[0].isRoot(info)) { diff --git a/src/server/project.ts b/src/server/project.ts index 88a08bfc863..5c199c2d812 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -937,7 +937,7 @@ namespace ts.server { this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { - if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) { + if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) { this.toggleJsInferredProject(/*isJsInferredProject*/ false); } } From 7173da2134d82bbd17c1aa2d97bd083fe63f9d88 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 15:38:47 -0700 Subject: [PATCH 069/144] Adding test for #16329 to verify the caching of file system when opening file --- .../unittests/tsserverProjectSystem.ts | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index cccbcfa32c0..86fe129a1c7 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3996,4 +3996,130 @@ namespace ts.projectSystem { } }); }); + + describe("CachingFileSystemInformation", () => { + function getFunctionWithCalledMapForSingleArgumentCb(cb: (f: string) => T) { + const calledMap = createMultiMap(); + return { + cb: (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); + }, + calledMap + }; + } + + function getFunctionWithCalledMapForFiveArgumentCb(cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => T) { + const calledMap = createMultiMap<[U, V, W, X]>(); + return { + cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1, arg2, arg3, arg4]); + return cb(f, arg1, arg2, arg3, arg4); + }, + calledMap + }; + } + + function checkMultiMapKeysForSingleEntry(caption: string, multiMap: MultiMap, expectedKeys: string[]) { + assert.equal(multiMap.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(multiMap.keys())} Expected: ${expectedKeys}`); + for (const name of expectedKeys) { + assert.isTrue(multiMap.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(multiMap.keys())}`); + assert.equal(multiMap.get(name).length, 1, `${caption} is expected to have just one entry for key ${name}, actual entry: ${multiMap.get(name)}`); + } + } + + it("when calling goto definition of module", () => { + const clientFile: FileOrFolder = { + path: "/a/b/controllers/vessels/client.ts", + content: ` + import { Vessel } from '~/models/vessel'; + const v = new Vessel(); + ` + }; + const anotherModuleFile: FileOrFolder = { + path: "/a/b/utils/db.ts", + content: "export class Bookshelf { }" + }; + const moduleFile: FileOrFolder = { + path: "/a/b/models/vessel.ts", + content: ` + import { Bookshelf } from '~/utils/db'; + export class Vessel extends Bookshelf {} + ` + }; + const tsconfigFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + target: "es6", + module: "es6", + baseUrl: "./", // all paths are relative to the baseUrl + paths: { + "~/*": ["*"] // resolve any `~/foo/bar` to `/foo/bar` + } + }, + exclude: [ + "api", + "build", + "node_modules", + "public", + "seeds", + "sql_updates", + "tests.build" + ] + }) + }; + + const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; + const host = createServerHost(projectFiles); + const session = createSession(host); + const projectService = session.getProjectService(); + const { configFileName } = projectService.openClientFile(clientFile.path); + + assert.isDefined(configFileName, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(tsconfigFile.path); + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + + const fileExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.fileExists.bind(host)); + host.fileExists = fileExistsCalledOn.cb; + const directoryExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.directoryExists.bind(host)); + host.directoryExists = directoryExistsCalledOn.cb; + const getDirectoriesCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.getDirectories.bind(host)); + host.getDirectories = getDirectoriesCalledOn.cb; + const readFileCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.readFile.bind(host)); + host.readFile = readFileCalledOn.cb; + const readDirectoryCalledOn = getFunctionWithCalledMapForFiveArgumentCb, ReadonlyArray, ReadonlyArray, number>(host.readDirectory.bind(host)); + host.readDirectory = readDirectoryCalledOn.cb; + + + // Get definitions shouldnt make host requests + const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { + file: clientFile.path, + position: clientFile.content.indexOf("/vessel") + 1, + line: undefined, + offset: undefined + }); + const { response } = session.executeCommand(getDefinitionRequest); + assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); + assert.equal(fileExistsCalledOn.calledMap.size, 0, `fileExists shouldnt be called`); + assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); + assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); + assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); + assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + + // Open the file should call only file exists on module directory and use cached value for parental directory + const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); + assert.equal(config2, configFileName); + checkMultiMapKeysForSingleEntry("fileExists", fileExistsCalledOn.calledMap, ["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); + assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); + assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); + assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); + assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); + }); + }); } From e500be28cdb54c0644db1b1efb1e817263771a07 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 17:28:38 -0700 Subject: [PATCH 070/144] Adding test for #16456 to verify watched directories in case-sensitive and non sensitive file system and fixing caching --- src/compiler/core.ts | 36 +-- src/compiler/resolutionCache.ts | 12 +- src/compiler/watchedProgram.ts | 4 +- .../unittests/cachingInServerLSHost.ts | 2 +- src/harness/unittests/tscWatchMode.ts | 5 +- .../unittests/tsserverProjectSystem.ts | 248 +++++++++++++++--- src/harness/virtualFileSystemWithWatch.ts | 9 +- src/server/editorServices.ts | 15 +- src/server/project.ts | 11 +- 9 files changed, 259 insertions(+), 83 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c93c9639ab2..00e8065a9f9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2076,8 +2076,8 @@ namespace ts { } export interface FileSystemEntries { - files: ReadonlyArray; - directories: ReadonlyArray; + readonly files: ReadonlyArray; + readonly directories: ReadonlyArray; } export interface FileMatcherPatterns { @@ -2644,8 +2644,13 @@ namespace ts { export interface CachedPartialSystem extends PartialSystem, CachedHost { } + interface MutableFileSystemEntries { + readonly files: string[]; + readonly directories: string[]; + } + export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { - const cachedReadDirectoryResult = createMap(); + const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { @@ -2665,11 +2670,11 @@ namespace ts { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } - function getCachedFileSystemEntries(rootDirPath: Path): FileSystemEntries | undefined { + function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { return cachedReadDirectoryResult.get(rootDirPath); } - function getCachedFileSystemEntriesForBaseDir(path: Path): FileSystemEntries | undefined { + function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { return getCachedFileSystemEntries(getDirectoryPath(path)); } @@ -2678,8 +2683,8 @@ namespace ts { } function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { - const resultFromHost: FileSystemEntries = { - files: host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]) || [], + const resultFromHost: MutableFileSystemEntries = { + files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], directories: host.getDirectories(rootDir) || [] }; @@ -2692,7 +2697,7 @@ namespace ts { * Otherwise gets result from host and caches it. * The host request is done under try catch block to avoid caching incorrect result */ - function tryReadDirectory(rootDir: string, rootDirPath: Path): FileSystemEntries | undefined { + function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { const cachedResult = getCachedFileSystemEntries(rootDirPath); if (cachedResult) { return cachedResult; @@ -2716,16 +2721,15 @@ namespace ts { return some(entries, file => fileNameEqual(file, name)); } - function updateFileSystemEntry(entries: ReadonlyArray, baseName: string, isValid: boolean) { + function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { if (hasEntry(entries, baseName)) { if (!isValid) { - return filter(entries, entry => !fileNameEqual(entry, baseName)); + return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); } } else if (isValid) { - return entries.concat(baseName); + return entries.push(baseName); } - return entries; } function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { @@ -2754,7 +2758,7 @@ namespace ts { const result = getCachedFileSystemEntriesForBaseDir(path); const baseFileName = getBaseNameOfFileName(dirPath); if (result) { - result.directories = updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); } host.createDirectory(dirPath); } @@ -2801,7 +2805,7 @@ namespace ts { const baseName = getBaseNameOfFileName(fileOrFolder); if (parentResult) { updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); - parentResult.directories = updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); + updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); } } } @@ -2818,8 +2822,8 @@ namespace ts { } } - function updateFilesOfFileSystemEntry(parentResult: FileSystemEntries, baseName: string, fileExists: boolean) { - parentResult.files = updateFileSystemEntry(parentResult.files, baseName, fileExists); + function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { + updateFileSystemEntry(parentResult.files, baseName, fileExists); } function clearCache() { diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 4fefd87a834..e331133fe74 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string): void; + invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; @@ -300,12 +300,12 @@ namespace ts { } function invalidateResolutionCacheOfChangedFailedLookupLocation( - failedLookupLocation: string, + failedLookupLocationPath: Path, cache: Map>) { cache.forEach((value, containingFile) => { if (value) { value.forEach(resolution => { - if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocation)) { + if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) { // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); @@ -320,9 +320,9 @@ namespace ts { invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } - function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation: string) { - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocation, resolvedTypeReferenceDirectives); + function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) { + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames); + invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives); } } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index bd2473a699d..084c707cea8 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -564,7 +564,7 @@ namespace ts { function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); - resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); scheduleProgramUpdate(); } @@ -633,7 +633,7 @@ namespace ts { // Reload is pending, do the reload if (!needsReload) { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); - if (!configFileSpecs.filesSpecs) { + if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 079eabebc1e..b7b382e5a1d 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -61,7 +61,7 @@ namespace ts { typingsInstaller: undefined }; const projectService = new server.ProjectService(svcOpts); - const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined); + const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, serverHost); const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index e8cba10f088..f5ed08b9b87 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -130,7 +130,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host); - checkProgramActualFiles(watch(), [f1.path]); + checkProgramActualFiles(watch(), [combinePaths(getDirectoryPath(upperCaseConfigFilePath), getBaseFileName(f1.path))]); }); it("create configured project without file list", () => { @@ -722,12 +722,13 @@ namespace ts.tscWatch { const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); createWatchModeWithConfigFile(configFile.path, host); - const error = `error TS6053: File '${moduleFile.path}' not found.${host.newLine}`; + const error = "a/b/file1.ts(1,20): error TS2307: Cannot find module \'./moduleFile\'.\n"; checkOutputDoesNotContain(host, [error]); const moduleFileOldPath = moduleFile.path; const moduleFileNewPath = "/a/b/moduleFile1.ts"; moduleFile.path = moduleFileNewPath; + host.clearOutput(); host.reloadFS([moduleFile, file1, configFile, libFile]); host.runQueuedTimeoutCallbacks(); checkOutputContains(host, [error]); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 86fe129a1c7..37cbd20521d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3998,33 +3998,206 @@ namespace ts.projectSystem { }); describe("CachingFileSystemInformation", () => { - function getFunctionWithCalledMapForSingleArgumentCb(cb: (f: string) => T) { - const calledMap = createMultiMap(); + type CalledMaps = { + fileExists: MultiMap; + directoryExists: MultiMap; + getDirectories: MultiMap; + readFile: MultiMap; + readDirectory: MultiMap<[ReadonlyArray, ReadonlyArray, ReadonlyArray, number]>; + }; + + function createCallsTrackingHost(host: TestServerHost) { + const keys: Array = ["fileExists", "directoryExists", "getDirectories", "readFile", "readDirectory"]; + const calledMaps = getCallsTrackingMap(); return { - cb: (f: string) => { - calledMap.add(f, /*value*/ true); - return cb(f); - }, - calledMap + verifyNoCall, + verifyCalledOnEachEntryOnce, + verifyCalledOnEachEntry, + verifyNoHostCalls, + verifyNoHostCallsExceptFileExistsOnce, + clear }; + + function getCallsTrackingMap() { + const calledMaps: { [s: string]: Map } = {}; + for (let i = 0; i < keys.length - 1; i++) { + setCallsTrackingWithSingleArgFn(keys[i]); + } + setCallsTrackingWithFiveArgFn(keys[keys.length - 1]); + return calledMaps as CalledMaps; + + function setCallsTrackingWithSingleArgFn(prop: keyof CalledMaps) { + const calledMap = createMultiMap(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string) => { + calledMap.add(f, /*value*/ true); + return cb(f); + }; + calledMaps[prop] = calledMap; + } + + function setCallsTrackingWithFiveArgFn(prop: keyof CalledMaps) { + const calledMap = createMultiMap<[U, V, W, X]>(); + const cb = (host)[prop].bind(host); + (host)[prop] = (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { + calledMap.add(f, [arg1, arg2, arg3, arg4]); + return cb(f, arg1, arg2, arg3, arg4); + }; + calledMaps[prop] = calledMap; + } + } + + function verifyNoCall(callback: keyof CalledMaps) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`); + } + + function verifyCalledOnEachEntry(callback: keyof CalledMaps, expectedKeys: Map) { + const calledMap = calledMaps[callback]; + assert.equal(calledMap.size, expectedKeys.size, `${callback}: incorrect size of map: Actual keys: ${arrayFrom(calledMap.keys())} Expected: ${arrayFrom(expectedKeys.keys())}`); + expectedKeys.forEach((called, name) => { + assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`); + assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`); + }); + } + + function verifyCalledOnEachEntryOnce(callback: keyof CalledMaps, expectedKeys: string[]) { + return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => 1))); + } + + function verifyNoHostCalls() { + for (const key of keys) { + verifyNoCall(key); + } + } + + function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) { + verifyCalledOnEachEntryOnce("fileExists", expectedKeys); + verifyNoCall("directoryExists"); + verifyNoCall("getDirectories"); + verifyNoCall("readFile"); + verifyNoCall("readDirectory"); + } + + function clear() { + for (const key of keys) { + calledMaps[key].clear(); + } + } } - function getFunctionWithCalledMapForFiveArgumentCb(cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => T) { - const calledMap = createMultiMap<[U, V, W, X]>(); - return { - cb: (f: string, arg1?: U, arg2?: V, arg3?: W, arg4?: X) => { - calledMap.add(f, [arg1, arg2, arg3, arg4]); - return cb(f, arg1, arg2, arg3, arg4); - }, - calledMap + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); + const file1: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" }; - } + const file2: FileOrFolder = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: FileOrFolder = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const tsconfigFile: FileOrFolder = { + path: `${frontendDir}/tsconfig.json`, + content: JSON.stringify({ + "compilerOptions": { + "strict": true, + "strictNullChecks": true, + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "noEmitOnError": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": [ + "node", + "jest" + ], + "noUnusedLocals": true, + "outDir": "./compiled", + "typeRoots": [ + "types", + "node_modules/@types" + ], + "baseUrl": ".", + "paths": { + "*": [ + "types/*" + ] + } + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "compiled" + ] + }) + }; + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host); + const canonicalConfigPath = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); + const { configFileName } = projectService.openClientFile(file1.path); + assert.equal(configFileName, tsconfigFile.path, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); - function checkMultiMapKeysForSingleEntry(caption: string, multiMap: MultiMap, expectedKeys: string[]) { - assert.equal(multiMap.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(multiMap.keys())} Expected: ${expectedKeys}`); - for (const name of expectedKeys) { - assert.isTrue(multiMap.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(multiMap.keys())}`); - assert.equal(multiMap.get(name).length, 1, `${caption} is expected to have just one entry for key ${name}, actual entry: ${multiMap.get(name)}`); + const project = projectService.configuredProjects.get(canonicalConfigPath); + verifyProjectAndWatchedDirectories(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Create file cookie.ts + projectFiles.push(file3); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + + const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); + callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]); + + // Called for type root resolution + const directoryExistsCalled = createMap(); + for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) { + directoryExistsCalled.set(`${dir}/node_modules`, 2); + } + directoryExistsCalled.set(`/node_modules`, 2); + directoryExistsCalled.set(`${frontendDir}/types`, 2); + directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); + directoryExistsCalled.set(canonicalFile3Path, 1); + callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); + + callsTrackingHost.verifyNoCall("getDirectories"); + callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]); + callsTrackingHost.verifyNoCall("readDirectory"); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + + callsTrackingHost.clear(); + + const { configFileName: configFile2 } = projectService.openClientFile(file3.path); + assert.equal(configFile2, configFileName); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + callsTrackingHost.verifyNoHostCalls(); + + function verifyProjectAndWatchedDirectories() { + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); } } @@ -4069,7 +4242,6 @@ namespace ts.projectSystem { ] }) }; - const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; const host = createServerHost(projectFiles); const session = createSession(host); @@ -4082,17 +4254,7 @@ namespace ts.projectSystem { const project = projectService.configuredProjects.get(tsconfigFile.path); checkProjectActualFiles(project, map(projectFiles, f => f.path)); - const fileExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.fileExists.bind(host)); - host.fileExists = fileExistsCalledOn.cb; - const directoryExistsCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.directoryExists.bind(host)); - host.directoryExists = directoryExistsCalledOn.cb; - const getDirectoriesCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.getDirectories.bind(host)); - host.getDirectories = getDirectoriesCalledOn.cb; - const readFileCalledOn = getFunctionWithCalledMapForSingleArgumentCb(host.readFile.bind(host)); - host.readFile = readFileCalledOn.cb; - const readDirectoryCalledOn = getFunctionWithCalledMapForFiveArgumentCb, ReadonlyArray, ReadonlyArray, number>(host.readDirectory.bind(host)); - host.readDirectory = readDirectoryCalledOn.cb; - + const callsTrackingHost = createCallsTrackingHost(host); // Get definitions shouldnt make host requests const getDefinitionRequest = makeSessionRequest(protocol.CommandTypes.Definition, { @@ -4103,23 +4265,23 @@ namespace ts.projectSystem { }); const { response } = session.executeCommand(getDefinitionRequest); assert.equal(response[0].file, moduleFile.path, "Should go to definition of vessel: response: " + JSON.stringify(response)); - assert.equal(fileExistsCalledOn.calledMap.size, 0, `fileExists shouldnt be called`); - assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); - assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); - assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); - assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + callsTrackingHost.verifyNoHostCalls(); // Open the file should call only file exists on module directory and use cached value for parental directory const { configFileName: config2 } = projectService.openClientFile(moduleFile.path); assert.equal(config2, configFileName); - checkMultiMapKeysForSingleEntry("fileExists", fileExistsCalledOn.calledMap, ["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); - assert.equal(directoryExistsCalledOn.calledMap.size, 0, `directoryExists shouldnt be called`); - assert.equal(getDirectoriesCalledOn.calledMap.size, 0, `getDirectories shouldnt be called`); - assert.equal(readFileCalledOn.calledMap.size, 0, `readFile shouldnt be called`); - assert.equal(readDirectoryCalledOn.calledMap.size, 0, `readDirectory shouldnt be called`); + callsTrackingHost.verifyNoHostCallsExceptFileExistsOnce(["/a/b/models/tsconfig.json", "/a/b/models/jsconfig.json"]); checkNumberOfConfiguredProjects(projectService, 1); assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); }); + + it("WatchDirectories for config file With non case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); + + it("WatchDirectories for config file With case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index b77618df360..44beb83922f 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -197,9 +197,12 @@ namespace ts.TestFSWithWatch { this.reloadFS(fileOrFolderList); } + toNormalizedAbsolutePath(s: string) { + return getNormalizedAbsolutePath(s, this.currentDirectory); + } + toFullPath(s: string) { - const fullPath = getNormalizedAbsolutePath(s, this.currentDirectory); - return this.toPath(fullPath); + return this.toPath(this.toNormalizedAbsolutePath(s)); } reloadFS(fileOrFolderList: FileOrFolder[]) { @@ -413,7 +416,7 @@ namespace ts.TestFSWithWatch { } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return ts.matchFiles(this.toFullPath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { + return ts.matchFiles(this.toNormalizedAbsolutePath(path), extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const directories: string[] = []; const files: string[] = []; const dirEntry = this.fs.get(this.toPath(dir)); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index fb4419df79d..6cbc5263249 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1429,7 +1429,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1583,9 +1583,12 @@ namespace ts.server { * @param uncheckedFileName is absolute pathname * @param fileContent is a known version of the file content that is more up to date than the one on disk */ - - getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { - return this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName), openedByClient, fileContent, scriptKind); + /*@internal*/ + getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) { + return this.getOrCreateScriptInfoForNormalizedPath( + toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined, + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn + ); } getScriptInfo(uncheckedFileName: string) { @@ -1610,11 +1613,11 @@ namespace ts.server { } } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) { const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { - if (openedByClient || this.host.fileExists(fileName)) { + if (openedByClient || (hostToQueryFileExistsOn || this.host).fileExists(fileName)) { info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path); this.filenameToScriptInfo.set(info.path, info); diff --git a/src/server/project.ts b/src/server/project.ts index d8344bd6a2c..b35298b2938 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -240,7 +240,7 @@ namespace ts.server { if (this.projectKind === ProjectKind.Configured) { (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocation); + this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); }); @@ -701,7 +701,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host); scriptInfo.attachToProject(this); }, removed => { @@ -744,7 +744,7 @@ namespace ts.server { } getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); if (existingValue !== undefined && existingValue !== scriptInfo) { @@ -758,7 +758,10 @@ namespace ts.server { } getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath( + fileName, /*openedByClient*/ false, /*fileContent*/ undefined, + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host + ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); } From 6227a36ff0f9879306e47c014de3471a0fc0fc28 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 21 Aug 2017 14:55:33 -0700 Subject: [PATCH 071/144] In Server when polling the file stat's do not send changed event in case the file doesnt exist. --- src/server/server.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/server/server.ts b/src/server/server.ts index 0fe37dd8ba0..0ccff7c4d25 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -538,7 +538,15 @@ namespace ts.server { fs.stat(watchedFile.fileName, (err: any, stats: any) => { if (err) { - watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed); + if (err.code === "ENOENT") { + if (watchedFile.mtime.getTime() !== 0) { + watchedFile.mtime = new Date(0); + watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Deleted); + } + } + else { + watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed); + } } else { const oldTime = watchedFile.mtime.getTime(); From 55931c46bb690ba7187767006219f9ca186ac794 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 21 Aug 2017 15:09:03 -0700 Subject: [PATCH 072/144] Update the failed lookup watches without doing lookups. This helps in not having to deal with duplicate locations and checking if there exists watch Anyways the watches are refCount based so we would just addref and remove ref on the same watches --- src/compiler/resolutionCache.ts | 32 ++++---------------------------- src/server/editorServices.ts | 2 +- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e331133fe74..6d7c1f6c0e4 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -240,35 +240,11 @@ namespace ts { } function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray | undefined, failedLookupLocations: ReadonlyArray) { - if (failedLookupLocations) { - if (existingFailedLookupLocations) { - const existingWatches = arrayToMap(existingFailedLookupLocations, toPath); - for (const failedLookupLocation of failedLookupLocations) { - const failedLookupLocationPath = toPath(failedLookupLocation); - if (existingWatches && existingWatches.has(failedLookupLocationPath)) { - // still has same failed lookup location, keep the watch - existingWatches.delete(failedLookupLocationPath); - } - else { - // Create new watch - watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name); - } - } + // Watch all the failed lookup locations + withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation); - // Close all the watches that are still present in the existingWatches since those are not the locations looked up for buy new resolution - existingWatches.forEach((failedLookupLocation, failedLookupLocationPath: Path) => - closeFailedLookupLocationWatcher(failedLookupLocation, failedLookupLocationPath, containingFile, name) - ); - } - else { - // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation); - } - } - else { - // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher); - } + // Close existing watches for the failed locations + withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher); } function invalidateResolutionCacheOfDeletedFile( diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6cbc5263249..884ee518a30 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1023,7 +1023,7 @@ namespace ts.server { if (this.configuredProjects.has(canonicalConfigFilePath)) { watches.push(WatchType.ConfigFilePath); } - this.logger.info(`ConfigFilePresence:: Current Watches: ['${watches.join("','")}']:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); + this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } /** From e65df125f8b82f345f9e0eca5ed392ed62a1822b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 22 Aug 2017 10:28:02 -0700 Subject: [PATCH 073/144] Add test for #16955 which simulates npm install --- .../unittests/tsserverProjectSystem.ts | 421 +++++++++++++----- 1 file changed, 302 insertions(+), 119 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 37cbd20521d..58c2b3a614d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4086,121 +4086,6 @@ namespace ts.projectSystem { } } - function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { - const frontendDir = "/Users/someuser/work/applications/frontend"; - const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); - const file1: FileOrFolder = { - path: `${frontendDir}/src/app/utils/Analytic.ts`, - content: "export class SomeClass { };" - }; - const file2: FileOrFolder = { - path: `${frontendDir}/src/app/redux/configureStore.ts`, - content: "export class configureStore { }" - }; - const file3: FileOrFolder = { - path: `${frontendDir}/src/app/utils/Cookie.ts`, - content: "export class Cookie { }" - }; - const es2016LibFile: FileOrFolder = { - path: "/a/lib/lib.es2016.full.d.ts", - content: libFile.content - }; - const tsconfigFile: FileOrFolder = { - path: `${frontendDir}/tsconfig.json`, - content: JSON.stringify({ - "compilerOptions": { - "strict": true, - "strictNullChecks": true, - "target": "es2016", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "noEmitOnError": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "types": [ - "node", - "jest" - ], - "noUnusedLocals": true, - "outDir": "./compiled", - "typeRoots": [ - "types", - "node_modules/@types" - ], - "baseUrl": ".", - "paths": { - "*": [ - "types/*" - ] - } - }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "compiled" - ] - }) - }; - const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; - const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host); - const canonicalConfigPath = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); - const { configFileName } = projectService.openClientFile(file1.path); - assert.equal(configFileName, tsconfigFile.path, `should find config`); - checkNumberOfConfiguredProjects(projectService, 1); - - const project = projectService.configuredProjects.get(canonicalConfigPath); - verifyProjectAndWatchedDirectories(); - - const callsTrackingHost = createCallsTrackingHost(host); - - // Create file cookie.ts - projectFiles.push(file3); - host.reloadFS(projectFiles); - host.runQueuedTimeoutCallbacks(); - - const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]); - - // Called for type root resolution - const directoryExistsCalled = createMap(); - for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) { - directoryExistsCalled.set(`${dir}/node_modules`, 2); - } - directoryExistsCalled.set(`/node_modules`, 2); - directoryExistsCalled.set(`${frontendDir}/types`, 2); - directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); - directoryExistsCalled.set(canonicalFile3Path, 1); - callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); - - callsTrackingHost.verifyNoCall("getDirectories"); - callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]); - callsTrackingHost.verifyNoCall("readDirectory"); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - - callsTrackingHost.clear(); - - const { configFileName: configFile2 } = projectService.openClientFile(file3.path); - assert.equal(configFile2, configFileName); - - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); - verifyProjectAndWatchedDirectories(); - callsTrackingHost.verifyNoHostCalls(); - - function verifyProjectAndWatchedDirectories() { - checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); - checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); - } - } - it("when calling goto definition of module", () => { const clientFile: FileOrFolder = { path: "/a/b/controllers/vessels/client.ts", @@ -4276,12 +4161,310 @@ namespace ts.projectSystem { assert.strictEqual(projectService.configuredProjects.get(tsconfigFile.path), project); }); - it("WatchDirectories for config file With non case sensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + describe("WatchDirectories for config file with", () => { + function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { + const frontendDir = "/Users/someuser/work/applications/frontend"; + const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); + const file1: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Analytic.ts`, + content: "export class SomeClass { };" + }; + const file2: FileOrFolder = { + path: `${frontendDir}/src/app/redux/configureStore.ts`, + content: "export class configureStore { }" + }; + const file3: FileOrFolder = { + path: `${frontendDir}/src/app/utils/Cookie.ts`, + content: "export class Cookie { }" + }; + const es2016LibFile: FileOrFolder = { + path: "/a/lib/lib.es2016.full.d.ts", + content: libFile.content + }; + const tsconfigFile: FileOrFolder = { + path: `${frontendDir}/tsconfig.json`, + content: JSON.stringify({ + "compilerOptions": { + "strict": true, + "strictNullChecks": true, + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "noEmitOnError": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "types": [ + "node", + "jest" + ], + "noUnusedLocals": true, + "outDir": "./compiled", + "typeRoots": [ + "types", + "node_modules/@types" + ], + "baseUrl": ".", + "paths": { + "*": [ + "types/*" + ] + } + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "compiled" + ] + }) + }; + const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; + const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); + const projectService = createProjectService(host); + const canonicalConfigPath = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); + const { configFileName } = projectService.openClientFile(file1.path); + assert.equal(configFileName, tsconfigFile.path, `should find config`); + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(canonicalConfigPath); + verifyProjectAndWatchedDirectories(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // Create file cookie.ts + projectFiles.push(file3); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + + const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); + callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]); + + // Called for type root resolution + const directoryExistsCalled = createMap(); + for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) { + directoryExistsCalled.set(`${dir}/node_modules`, 2); + } + directoryExistsCalled.set(`/node_modules`, 2); + directoryExistsCalled.set(`${frontendDir}/types`, 2); + directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); + directoryExistsCalled.set(canonicalFile3Path, 1); + callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); + + callsTrackingHost.verifyNoCall("getDirectories"); + callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]); + callsTrackingHost.verifyNoCall("readDirectory"); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + + callsTrackingHost.clear(); + + const { configFileName: configFile2 } = projectService.openClientFile(file3.path); + assert.equal(configFile2, configFileName); + + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(canonicalConfigPath), project); + verifyProjectAndWatchedDirectories(); + callsTrackingHost.verifyNoHostCalls(); + + function verifyProjectAndWatchedDirectories() { + checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); + checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); + } + } + + it("case insensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); + }); + + it("case sensitive file system", () => { + verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + }); }); - it("WatchDirectories for config file With case sensitive file system", () => { - verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ true); + describe("Verify npm install in directory with tsconfig file works when", () => { + function verifyNpmInstall(timeoutDuringPartialInstallation: boolean) { + const app: FileOrFolder = { + path: "/a/b/app.ts", + content: "import _ from 'lodash';" + }; + const tsconfigJson: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: '{ "compilerOptions": { } }' + }; + const packageJson: FileOrFolder = { + path: "/a/b/package.json", + content: ` +{ + "name": "test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "lodash", + "rxjs" + }, + "devDependencies": { + "@types/lodash", + "typescript" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} +` + }; + const appFolder = getDirectoryPath(app.path); + const projectFiles = [app, libFile, tsconfigJson]; + const otherFiles = [packageJson]; + const host = createServerHost(projectFiles.concat(otherFiles)); + const projectService = createProjectService(host); + const { configFileName } = projectService.openClientFile(app.path); + assert.equal(configFileName, tsconfigJson.path, `should find config`); + const watchedModuleLocations = getNodeModulesWatchedDirectories(appFolder, "lodash"); + verifyProject(); + + let timeoutAfterReloadFs = timeoutDuringPartialInstallation; + + // Simulate npm install + const filesAndFoldersToAdd: FileOrFolder[] = [ + { "path": "/a/b/node_modules" }, + { "path": "/a/b/node_modules/.staging/@types" }, + { "path": "/a/b/node_modules/.staging/lodash-b0733faa" }, + { "path": "/a/b/node_modules/.staging/@types/lodash-e56c4fe7" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61" }, + { "path": "/a/b/node_modules/.staging/typescript-8493ea5d" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff/package.json", "content": "{\n \"name\": \"symbol-observable\",\n \"version\": \"1.0.4\",\n \"description\": \"Symbol.observable ponyfill\",\n \"license\": \"MIT\",\n \"repository\": \"blesh/symbol-observable\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"engines\": {\n \"node\": \">=0.10.0\"\n },\n \"scripts\": {\n \"test\": \"npm run build && mocha && tsc ./ts-test/test.ts && node ./ts-test/test.js && check-es3-syntax -p lib/ --kill\",\n \"build\": \"babel es --out-dir lib\",\n \"prepublish\": \"npm test\"\n },\n \"files\": [\n \"" }, + { "path": "/a/b/node_modules/.staging/lodash-b0733faa/package.json", "content": "{\n \"name\": \"lodash\",\n \"version\": \"4.17.4\",\n \"description\": \"Lodash modular utilities.\",\n \"keywords\": \"modules, stdlib, util\",\n \"homepage\": \"https://lodash.com/\",\n \"repository\": \"lodash/lodash\",\n \"icon\": \"https://lodash.com/icon.svg\",\n \"license\": \"MIT\",\n \"main\": \"lodash.js\",\n \"author\": \"John-David Dalton (http://allyoucanleet.com/)\",\n \"contributors\": [\n \"John-David Dalton (http://allyoucanleet.com/)\",\n \"Mathias Bynens \",\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"license\": \"Apache-2.0\",\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"typings\": \"Rx.d.ts\",\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n }\n}" }, + { "path": "/a/b/node_modules/.staging/typescript-8493ea5d/package.json", "content": "{\n \"name\": \"typescript\",\n \"author\": \"Microsoft Corp.\",\n \"homepage\": \"http://typescriptlang.org/\",\n \"version\": \"2.4.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"TypeScript is a language for application scale JavaScript development\",\n \"keywords\": [\n \"TypeScript\",\n \"Microsoft\",\n \"compiler\",\n \"language\",\n \"javascript\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/Microsoft/TypeScript/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Microsoft/TypeScript.git\"\n },\n \"main\": \"./lib/typescript.js\",\n \"typings\": \"./lib/typescript.d.ts\",\n \"bin\": {\n \"tsc\": \"./bin/tsc\",\n \"tsserver\": \"./bin/tsserver\"\n },\n \"engines\": {\n \"node\": \">=4.2.0\"\n },\n \"devDependencies\": {\n \"@types/browserify\": \"latest\",\n \"@types/chai\": \"latest\",\n \"@types/convert-source-map\": \"latest\",\n \"@types/del\": \"latest\",\n \"@types/glob\": \"latest\",\n \"@types/gulp\": \"latest\",\n \"@types/gulp-concat\": \"latest\",\n \"@types/gulp-help\": \"latest\",\n \"@types/gulp-newer\": \"latest\",\n \"@types/gulp-sourcemaps\": \"latest\",\n \"@types/merge2\": \"latest\",\n \"@types/minimatch\": \"latest\",\n \"@types/minimist\": \"latest\",\n \"@types/mkdirp\": \"latest\",\n \"@types/mocha\": \"latest\",\n \"@types/node\": \"latest\",\n \"@types/q\": \"latest\",\n \"@types/run-sequence\": \"latest\",\n \"@types/through2\": \"latest\",\n \"browserify\": \"latest\",\n \"chai\": \"latest\",\n \"convert-source-map\": \"latest\",\n \"del\": \"latest\",\n \"gulp\": \"latest\",\n \"gulp-clone\": \"latest\",\n \"gulp-concat\": \"latest\",\n \"gulp-help\": \"latest\",\n \"gulp-insert\": \"latest\",\n \"gulp-newer\": \"latest\",\n \"gulp-sourcemaps\": \"latest\",\n \"gulp-typescript\": \"latest\",\n \"into-stream\": \"latest\",\n \"istanbul\": \"latest\",\n \"jake\": \"latest\",\n \"merge2\": \"latest\",\n \"minimist\": \"latest\",\n \"mkdirp\": \"latest\",\n \"mocha\": \"latest\",\n \"mocha-fivemat-progress-reporter\": \"latest\",\n \"q\": \"latest\",\n \"run-sequence\": \"latest\",\n \"sorcery\": \"latest\",\n \"through2\": \"latest\",\n \"travis-fold\": \"latest\",\n \"ts-node\": \"latest\",\n \"tslint\": \"latest\",\n \"typescript\": \"^2.4\"\n },\n \"scripts\": {\n \"pretest\": \"jake tests\",\n \"test\": \"jake runtests-parallel\",\n \"build\": \"npm run build:compiler && npm run build:tests\",\n \"build:compiler\": \"jake local\",\n \"build:tests\": \"jake tests\",\n \"start\": \"node lib/tsc\",\n \"clean\": \"jake clean\",\n \"gulp\": \"gulp\",\n \"jake\": \"jake\",\n \"lint\": \"jake lint\",\n \"setup-hooks\": \"node scripts/link-hooks.js\"\n },\n \"browser\": {\n \"buffer\": false,\n \"fs\": false,\n \"os\": false,\n \"path\": false\n }\n}" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.js", "content": "module.exports = require('./lib/index');\n" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff/index.d.ts", "content": "declare const observableSymbol: symbol;\nexport default observableSymbol;\n" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib" }, + { "path": "/a/b/node_modules/.staging/symbol-observable-24bcbbff/lib/index.js", "content": "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _ponyfill = require('./ponyfill');\n\nvar _ponyfill2 = _interopRequireDefault(_ponyfill);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar root; /* global window */\n\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = (0, _ponyfill2['default'])(root);\nexports['default'] = result;" }, + ]; + verifyAfterPartialOrCompleteNpmInstall(2); + + filesAndFoldersToAdd.push( + { "path": "/a/b/node_modules/.staging/typescript-8493ea5d/lib" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/add/operator" }, + { "path": "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/package.json", "content": "{\n \"name\": \"@types/lodash\",\n \"version\": \"4.14.74\",\n \"description\": \"TypeScript definitions for Lo-Dash\",\n \"license\": \"MIT\",\n \"contributors\": [\n {\n \"name\": \"Brian Zengel\",\n \"url\": \"https://github.com/bczengel\"\n },\n {\n \"name\": \"Ilya Mochalov\",\n \"url\": \"https://github.com/chrootsu\"\n },\n {\n \"name\": \"Stepan Mikhaylyuk\",\n \"url\": \"https://github.com/stepancar\"\n },\n {\n \"name\": \"Eric L Anderson\",\n \"url\": \"https://github.com/ericanderson\"\n },\n {\n \"name\": \"AJ Richardson\",\n \"url\": \"https://github.com/aj-r\"\n },\n {\n \"name\": \"Junyoung Clare Jang\",\n \"url\": \"https://github.com/ailrun\"\n }\n ],\n \"main\": \"\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://www.github.com/DefinitelyTyped/DefinitelyTyped.git\"\n },\n \"scripts\": {},\n \"dependencies\": {},\n \"typesPublisherContentHash\": \"12af578ffaf8d86d2df37e591857906a86b983fa9258414326544a0fe6af0de8\",\n \"typeScriptVersion\": \"2.2\"\n}" }, + { "path": "/a/b/node_modules/.staging/lodash-b0733faa/index.js", "content": "module.exports = require('./lodash');" }, + { "path": "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" } + ); + // Since we didnt add any supported extension file, there wont be any timeout scheduled + verifyAfterPartialOrCompleteNpmInstall(0); + + // Remove file "/a/b/node_modules/.staging/typescript-8493ea5d/package.json.3017591594" + filesAndFoldersToAdd.length--; + verifyAfterPartialOrCompleteNpmInstall(0); + + filesAndFoldersToAdd.push( + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, + { "path": "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", "content": "// Type definitions for Lo-Dash 4.14\n// Project: http://lodash.com/\n// Definitions by: Brian Zengel ,\n// Ilya Mochalov ,\n// Stepan Mikhaylyuk ,\n// Eric L Anderson ,\n// AJ Richardson ,\n// Junyoung Clare Jang \n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n// TypeScript Version: 2.2\n\n/**\n### 4.0.0 Changelog (https://github.com/lodash/lodash/wiki/Changelog)\n\n#### TODO:\nremoved:\n- [x] Removed _.support\n- [x] Removed _.findWhere in favor of _.find with iteratee shorthand\n- [x] Removed _.where in favor of _.filter with iteratee shorthand\n- [x] Removed _.pluck in favor of _.map with iteratee shorthand\n\nrenamed:\n- [x] Renamed _.first to _.head\n- [x] Renamed _.indexBy to _.keyBy\n- [x] Renamed _.invoke to _.invokeMap\n- [x] Renamed _.overArgs to _.overArgs\n- [x] Renamed _.padLeft & _.padRight to _.padStart & _.padEnd\n- [x] Renamed _.pairs to _.toPairs\n- [x] Renamed _.rest to _.tail\n- [x] Renamed _.restParam to _.rest\n- [x] Renamed _.sortByOrder to _.orderBy\n- [x] Renamed _.trimLeft & _.trimRight to _.trimStart & _.trimEnd\n- [x] Renamed _.trunc to _.truncate\n\nsplit:\n- [x] Split _.indexOf & _.lastIndexOf into _.sortedIndexOf & _.sortedLastIndexOf\n- [x] Split _.max & _.min into _.maxBy & _.minBy\n- [x] Split _.omit & _.pick into _.omitBy & _.pickBy\n- [x] Split _.sample into _.sampleSize\n- [x] Split _.sortedIndex into _.sortedIndexBy\n- [x] Split _.sortedLastIndex into _.sortedLastIndexBy\n- [x] Split _.uniq into _.sortedUniq, _.sortedUniqBy, & _.uniqBy\n\nchanges:\n- [x] Absorbed _.sortByAll into _.sortBy\n- [x] Changed the category of _.at to “Object”\n- [x] Changed the category of _.bindAll to “Utility”\n- [x] Made _.capitalize uppercase the first character & lowercase the rest\n- [x] Made _.functions return only own method names\n\nadded 23 array methods:\n- [x] _.concat\n- [x] _.differenceBy\n- [x] _.differenceWith\n- [x] _.flatMap\n- [x] _.fromPairs\n- [x] _.intersectionBy\n- [x] _.intersectionWith\n- [x] _.join\n- [x] _.pullAll\n- [x] _.pullAllBy\n- [x] _.reverse\n- [x] _.sortedIndexBy\n- [x] _.sortedIndexOf\n- [x] _.sortedLastIndexBy\n- [x] _.sortedLastIndexOf\n- [x] _.sortedUniq\n- [x] _.sortedUniqBy\n- [x] _.unionBy\n- [x] _.unionWith\n- [x] _.uniqBy\n- [x] _.uniqWith\n- [x] _.xorBy\n- [x] _.xorWith\n\nadded 20 lang methods:\n- [x] _.cloneDeepWith\n- [x] _.cloneWith\n- [x] _.eq\n- [x] _.isArrayLike\n- [x] _.isArrayLikeObject\n- [x] _.isEqualWith\n- [x] _.isInteger\n- [x] _.isLength\n- [x] _.isMatchWith\n- [x] _.isNil\n- [x] _.isObjectLike\n- [x] _.isSafeInteger\n- [x] _.isSymbol\n- [x] _.toInteger\n- [x] _.toLength\n- [x] _.toNumber\n- [x] _.toSafeInteger\n- [x] _.toString\n- [X] _.conforms\n- [X] _.conformsTo\n\nadded 13 object methods:\n- [x] _.assignIn\n- [x] _.assignInWith\n- [x] _.assignWith\n- [x] _.functionsIn\n- [x] _.hasIn\n- [x] _.mergeWith\n- [x] _.omitBy\n- [x] _.pickBy\n\nadded 8 string methods:\n- [x] _.lowerCase\n- [x] _.lowerFirst\n- [x] _.upperCase\n- [x] _.upperFirst\n- [x] _.toLower\n- [x] _.toUpper\n\nadded 8 utility methods:\n- [x] _.toPath\n\nadded 4 math methods:\n- [x] _.maxBy\n- [x] _.mean\n- [x] _.minBy\n- [x] _.sumBy\n\nadded 2 function methods:\n- [x] _.flip\n- [x] _.unary\n\nadded 2 number methods:\n- [x] _.clamp\n- [x] _.subtract\n\nadded collection method:\n- [x] _.sampleSize\n\nAdded 3 aliases\n\n- [x] _.first as an alias of _.head\n\nRemoved 17 aliases\n- [x] Removed aliase _.all\n- [x] Removed aliase _.any\n- [x] Removed aliase _.backflow\n- [x] Removed aliase _.callback\n- [x] Removed aliase _.collect\n- [x] Removed aliase _.compose\n- [x] Removed aliase _.contains\n- [x] Removed aliase _.detect\n- [x] Removed aliase _.foldl\n- [x] Removed aliase _.foldr\n- [x] Removed aliase _.include\n- [x] Removed aliase _.inject\n- [x] Removed aliase _.methods\n- [x] Removed aliase _.object\n- [x] Removed aliase _.run\n- [x] Removed aliase _.select\n- [x] Removed aliase _.unique\n\nOther changes\n- [x] Added support for array buffers to _.isEqual\n- [x] Added support for converting iterators to _.toArray\n- [x] Added support for deep paths to _.zipObject\n- [x] Changed UMD to export to window or self when available regardless of other exports\n- [x] Ensured debounce cancel clears args & thisArg references\n- [x] Ensured _.add, _.subtract, & _.sum don’t skip NaN values\n- [x] Ensured _.clone treats generators like functions\n- [x] Ensured _.clone produces clones with the source’s [[Prototype]]\n- [x] Ensured _.defaults assigns properties that shadow Object.prototype\n- [x] Ensured _.defaultsDeep doesn’t merge a string into an array\n- [x] Ensured _.defaultsDeep & _.merge don’t modify sources\n- [x] Ensured _.defaultsDeep works with circular references\n- [x] Ensured _.keys skips “length” on strict mode arguments objects in Safari 9\n- [x] Ensured _.merge doesn’t convert strings to arrays\n- [x] Ensured _.merge merges plain-objects onto non plain-objects\n- [x] Ensured _#plant resets iterator data of cloned sequences\n- [x] Ensured _.random swaps min & max if min is greater than max\n- [x] Ensured _.range preserves the sign of start of -0\n- [x] Ensured _.reduce & _.reduceRight use getIteratee in their array branch\n- [x] Fixed rounding issue with the precision param of _.floor\n- [x] Added flush method to debounced & throttled functions\n\n** LATER **\nMisc:\n- [ ] Made _.forEach, _.forIn, _.forOwn, & _.times implicitly end a chain sequence\n- [ ] Removed thisArg params from most methods\n- [ ] Made “By” methods provide a single param to iteratees\n- [ ] Made _.words chainable by default\n- [ ] Removed isDeep params from _.clone & _.flatten\n- [ ] Removed _.bindAll support for binding all methods when no names are provided\n- [ ] Removed func-first param signature from _.before & _.after\n- [ ] _.extend as an alias of _.assignIn\n- [ ] _.extendWith as an alias of _.assignInWith\n- [ ] Added clear method to _.memoize.Cache\n- [ ] Added support for ES6 maps, sets, & symbols to _.clone, _.isEqual, & _.toArray\n- [x] Enabled _.flow & _.flowRight to accept an array of functions\n- [ ] Ensured “Collection” methods treat functions as objects\n- [ ] Ensured _.assign, _.defaults, & _.merge coerce object values to objects\n- [ ] Ensured _.bindKey bound functions call object[key] when called with the new operator\n- [ ] Ensured _.isFunction returns true for generator functions\n- [ ] Ensured _.merge assigns typed arrays directly\n- [ ] Made _(...) an iterator & iterable\n- [ ] Made _.drop, _.take, & right forms coerce n of undefined to 0\n\nMethods:\n- [ ] _.concat\n- [ ] _.differenceBy\n- [ ] _.differenceWith\n- [ ] _.flatMap\n- [ ] _.fromPairs\n- [ ] _.intersectionBy\n- [ ] _.intersectionWith\n- [ ] _.join\n- [ ] _.pullAll\n- [ ] _.pullAllBy\n- [ ] _.reverse\n- [ ] _.sortedLastIndexOf\n- [ ] _.unionBy\n- [ ] _.unionWith\n- [ ] _.uniqWith\n- [ ] _.xorBy\n- [ ] _.xorWith\n- [ ] _.toString\n\n- [ ] _.invoke\n- [ ] _.setWith\n- [ ] _.toPairs\n- [ ] _.toPairsIn\n- [ ] _.unset\n\n- [ ] _.replace\n- [ ] _.split\n\n- [ ] _.cond\n- [ ] _.nthArg\n- [ ] _.over\n- [ ] _.overEvery\n- [ ] _.overSome\n- [ ] _.rangeRight\n\n- [ ] _.next\n*/\n\nexport = _;\nexport as namespace _;\n\ndeclare var _: _.LoDashStatic;\n\ntype PartialObject = Partial;\n\ndeclare namespace _ {\n type Many = T | T[];\n\n interface LoDashStatic {\n /**\n * Creates a lodash object which wraps the given value to enable intuitive method chaining.\n *\n * In addition to Lo-Dash methods, wrappers also have the following Array methods:\n * concat, join, pop, push, reverse, shift, slice, sort, splice, and unshift\n *\n * Chaining is supported in custom builds as long as the value method is implicitly or\n * explicitly included in the build.\n *\n * The chainable wrapper functions are:\n * after, assign, bind, bindAll, bindKey, chain, chunk, compact, compose, concat, countBy,\n * createCallback, curry, debounce, defaults, defer, delay, difference, filter, flatten,\n * forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, functions, groupBy,\n * keyBy, initial, intersection, invert, invoke, keys, map, max, memoize, merge, min,\n * object, omit, once, pairs, partial, partialRight, pick, pluck, pull, push, range, reject,\n * remove, rest, reverse, sample, shuffle, slice, sort, sortBy, splice, tap, throttle, times,\n * toArray, transform, union, uniq, unset, unshift, unzip, values, where, without, wrap, and zip\n *\n * The non-chainable wrapper functions are:\n * clone, cloneDeep, contains, escape, every, find, findIndex, findKey, findLast,\n * findLastIndex, findLastKey, has, identity, indexOf, isArguments, isArray, isBoolean,\n * isDate, isElement, isEmpty, isEqual, isFinite, isFunction, isNaN, isNull, isNumber,\n * isObject, isPlainObject, isRegExp, isString, isUndefined, join, lastIndexOf, mixin,\n * noConflict, parseInt, pop, random, reduce, reduceRight, result, shift, size, some,\n * sortedIndex, runInContext, template, unescape, uniqueId, and value\n *\n * The wrapper functions first and last return wrapped values when n is provided, otherwise\n * they return unwrapped values.\n *\n * Explicit chaining can be enabled by using the _.chain method.\n **/\n (value: number): LoDashImplicitWrapper;\n (value: string): LoDashImplicitStringWrapper;\n (value: boolean): LoDashImplicitWrapper;\n (value: null | undefined): LoDashImplicitWrapper;\n (value: number[]): LoDashImplicitNumberArrayWrapper;\n (value: T[]): LoDashImplicitArrayWrapper;\n (value: T[] | null | undefined): LoDashImplicitNillableArrayWrapper;\n (value: T): LoDashImplicitObjectWrapper;\n (value: T | null | undefined): LoDashImplicitNillableObjectWrapper;\n (value: any): LoDashImplicitWrapper;\n\n /**\n * The semantic version number.\n **/\n VERSION: string;\n\n /**\n * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby\n * (ERB). Change the following template settings to use alternative delimiters.\n **/\n templateSettings: TemplateSettings;\n }\n\n /**\n * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby\n * (ERB). Change the following template settings to use alternative delimiters.\n **/\n interface TemplateSettings {\n /**\n * The \"escape\" delimiter.\n **/\n escape?: RegExp;\n\n /**\n * The \"evaluate\" delimiter.\n **/\n evaluate?: RegExp;\n\n /**\n * An object to import into the template as local variables.\n **/\n imports?: Dictionary;\n\n /**\n * The \"interpolate\" delimiter.\n **/\n interpolate?: RegExp;\n\n /**\n * Used to reference the data object in the template text.\n **/\n variable?: string;\n }\n\n /**\n * Creates a cache object to store key/value pairs.\n */\n interface MapCache {\n /**\n * Removes `key` and its value from the cache.\n * @param key The key of the value to remove.\n * @return Returns `true` if the entry was removed successfully, else `false`.\n */\n delete(key: string): boolean;\n\n /**\n * Gets the cached value for `key`.\n * @param key The key of the value to get.\n * @return Returns the cached value.\n */\n get(key: string): any;\n\n /**\n * Checks if a cached value for `key` exists.\n * @param key The key of the entry to check.\n * @return Returns `true` if an entry for `key` exists, else `false`.\n */\n has(key: string): boolean;\n\n /**\n * Sets `value` to `key` of the cache.\n * @param key The key of the value to cache.\n * @param value The value to cache.\n * @return Returns the cache object.\n */\n set(key: string, value: any): _.Dictionary;\n\n /**\n * Removes all key-value entries from the map.\n */\n clear(): void;\n }\n interface MapCacheConstructor {\n new (): MapCache;\n }\n\n interface LoDashWrapperBase { }\n\n interface LoDashImplicitWrapperBase extends LoDashWrapperBase { }\n\n interface LoDashExplicitWrapperBase extends LoDashWrapperBase { }\n\n interface LoDashImplicitWrapper extends LoDashImplicitWrapperBase> { }\n\n interface LoDashExplicitWrapper extends LoDashExplicitWrapperBase> { }\n\n interface LoDashImplicitStringWrapper extends LoDashImplicitWrapper { }\n\n interface LoDashExplicitStringWrapper extends LoDashExplicitWrapper { }\n\n interface LoDashImplicitObjectWrapperBase extends LoDashImplicitWrapperBase { }\n\n interface LoDashImplicitObjectWrapper extends LoDashImplicitObjectWrapperBase> { }\n\n interface LoDashImplicitNillableObjectWrapper extends LoDashImplicitObjectWrapperBase> { }\n\n interface LoDashExplicitObjectWrapperBase extends LoDashExplicitWrapperBase { }\n\n interface LoDashExplicitObjectWrapper extends LoDashExplicitObjectWrapperBase> { }\n\n interface LoDashExplicitNillableObjectWrapper extends LoDashExplicitObjectWrapperBase> { }\n\n interface LoDashImplicitArrayWrapperBase extends LoDashImplicitWrapperBase {\n pop(): T | undefined;\n push(...items: T[]): TWrapper;\n shift(): T | undefined;\n sort(compareFn?: (a: T, b: T) => number): TWrapper;\n splice(start: number): TWrapper;\n splice(start: number, deleteCount: number, ...items: T[]): TWrapper;\n unshift(...items: T[]): TWrapper;\n }\n\n interface LoDashImplicitArrayWrapper extends LoDashImplicitArrayWrapperBase> { }\n\n interface LoDashImplicitNillableArrayWrapper extends LoDashImplicitArrayWrapperBase> { }\n\n interface LoDashImplicitNumberArrayWrapperBase extends LoDashImplicitArrayWrapperBase { }\n\n interface LoDashImplicitNumberArrayWrapper extends LoDashImplicitArrayWrapper { }\n\n interface LoDashExplicitArrayWrapperBase extends LoDashExplicitWrapperBase {\n pop(): LoDashExplicitObjectWrapper;\n push(...items: T[]): TWrapper;\n shift(): LoDashExplicitObjectWrapper;\n sort(compareFn?: (a: T, b: T) => number): TWrapper;\n splice(start: number): TWrapper;\n splice(start: number, deleteCount: number, ...items: T[]): TWrapper;\n unshift(...items: T[]): TWrapper;\n }\n\n interface LoDashExplicitArrayWrapper extends LoDashExplicitArrayWrapperBase> { }\n\n interface LoDashExplicitNillableArrayWrapper extends LoDashExplicitArrayWrapperBase> { }\n\n interface LoDashExplicitNumberArrayWrapper extends LoDashExplicitArrayWrapper { }\n\n /*********\n * Array *\n *********/\n\n //_.chunk\n interface LoDashStatic {\n /**\n * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the\n * final chunk will be the remaining elements.\n *\n * @param array The array to process.\n * @param size The length of each chunk.\n * @return Returns the new array containing chunks.\n */\n chunk(\n array: List | null | undefined,\n size?: number\n ): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.compact\n interface LoDashStatic {\n /**\n * Creates an array with all falsey values removed. The values false, null, 0, \"\", undefined, and NaN are\n * falsey.\n *\n * @param array The array to compact.\n * @return (Array) Returns the new array of filtered values.\n */\n compact(array?: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashExplicitArrayWrapper;\n }\n\n //_.concat DUMMY\n interface LoDashStatic {\n /**\n * Creates a new array concatenating `array` with any additional arrays\n * and/or values.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to concatenate.\n * @param {...*} [values] The values to concatenate.\n * @returns {Array} Returns the new concatenated array.\n * @example\n *\n * var array = [1];\n * var other = _.concat(array, 2, [3], [[4]]);\n *\n * console.log(other);\n * // => [1, 2, 3, [4]]\n *\n * console.log(array);\n * // => [1]\n */\n concat(array: List, ...values: Array>): T[];\n }\n\n //_.difference\n interface LoDashStatic {\n /**\n * Creates an array of unique array values not included in the other provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param array The array to inspect.\n * @param values The arrays of values to exclude.\n * @return Returns the new array of filtered values.\n */\n difference(\n array: List | null | undefined,\n ...values: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.differenceBy\n interface LoDashStatic {\n /**\n * This method is like _.difference except that it accepts iteratee which is invoked for each element of array\n * and values to generate the criterion by which uniqueness is computed. The iteratee is invoked with one\n * argument: (value).\n *\n * @param array The array to inspect.\n * @param values The values to exclude.\n * @param iteratee The iteratee invoked per element.\n * @returns Returns the new array of filtered values.\n */\n differenceBy(\n array: List | null | undefined,\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n ...values: any[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.differenceWith DUMMY\n interface LoDashStatic {\n /**\n * Creates an array of unique `array` values not included in the other\n * provided arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {...Array} [values] The values to exclude.\n * @returns {Array} Returns the new array of filtered values.\n * @example\n *\n * _.difference([3, 2, 1], [4, 2]);\n * // => [3, 1]\n */\n differenceWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.drop\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements dropped from the beginning.\n *\n * @param array The array to query.\n * @param n The number of elements to drop.\n * @return Returns the slice of array.\n */\n drop(array: List | null | undefined, n?: number): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.dropRight\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements dropped from the end.\n *\n * @param array The array to query.\n * @param n The number of elements to drop.\n * @return Returns the slice of array.\n */\n dropRight(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.dropRightWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array excluding elements dropped from the end. Elements are dropped until predicate\n * returns falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * match the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.dropWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array excluding elements dropped from the beginning. Elements are dropped until predicate\n * returns falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.fill\n interface LoDashStatic {\n /**\n * Fills elements of array with value from start up to, but not including, end.\n *\n * Note: This method mutates array.\n *\n * @param array The array to fill.\n * @param value The value to fill array with.\n * @param start The start position.\n * @param end The end position.\n * @return Returns array.\n */\n fill(\n array: any[] | null | undefined,\n value: T,\n start?: number,\n end?: number\n ): T[];\n\n /**\n * @see _.fill\n */\n fill(\n array: List | null | undefined,\n value: T,\n start?: number,\n end?: number\n ): List;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.findIndex\n interface LoDashStatic {\n /**\n * This method is like _.find except that it returns the index of the first element predicate returns truthy\n * for instead of the element itself.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the index of the found element, else -1.\n */\n findIndex(\n array: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n array: List | null | undefined,\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n array: List | null | undefined,\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.findLastIndex\n interface LoDashStatic {\n /**\n * This method is like _.findIndex except that it iterates over elements of collection from right to left.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the index of the found element, else -1.\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.first\n interface LoDashStatic {\n /**\n * @see _.head\n */\n first(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.head\n */\n first(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n first(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n first(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.head\n */\n first(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n first(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n first(): T;\n }\n\n interface RecursiveArray extends Array> {}\n interface ListOfRecursiveArraysOrValues extends List> {}\n\n //_.flatten\n interface LoDashStatic {\n /**\n * Flattens a nested array. If isDeep is true the array is recursively flattened, otherwise it’s only\n * flattened a single level.\n *\n * @param array The array to flatten.\n * @param isDeep Specify a deep flatten.\n * @return Returns the new flattened array.\n */\n flatten(array: ListOfRecursiveArraysOrValues | null | undefined, isDeep: boolean): T[];\n\n /**\n * @see _.flatten\n */\n flatten(array: List> | null | undefined): T[];\n\n /**\n * @see _.flatten\n */\n flatten(array: ListOfRecursiveArraysOrValues | null | undefined): RecursiveArray;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flatten\n */\n flatten(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flatten\n */\n flatten(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashExplicitArrayWrapper;\n }\n\n //_.flattenDeep\n interface LoDashStatic {\n /**\n * Recursively flattens a nested array.\n *\n * @param array The array to recursively flatten.\n * @return Returns the new flattened array.\n */\n flattenDeep(array: ListOfRecursiveArraysOrValues | null | undefined): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n // _.flattenDepth\n interface LoDashStatic {\n /**\n * Recursively flatten array up to depth times.\n *\n * @param array The array to recursively flatten.\n * @param number The maximum recursion depth.\n * @return Returns the new flattened array.\n */\n flattenDepth(array: ListOfRecursiveArraysOrValues | null | undefined, depth?: number): T[];\n }\n\n //_.fromPairs\n interface LoDashStatic {\n /**\n * The inverse of `_.toPairs`; this method returns an object composed\n * from key-value `pairs`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} pairs The key-value pairs.\n * @returns {Object} Returns the new object.\n * @example\n *\n * _.fromPairs([['fred', 30], ['barney', 40]]);\n * // => { 'fred': 30, 'barney': 40 }\n */\n fromPairs(\n array: List<[_.StringRepresentable, T]> | null | undefined\n ): Dictionary;\n\n /**\n @see _.fromPairs\n */\n fromPairs(\n array: List | null | undefined\n ): Dictionary;\n }\n\n //_.fromPairs DUMMY\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.fromPairs\n */\n fromPairs(): LoDashImplicitObjectWrapper;\n }\n\n //_.fromPairs DUMMY\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.fromPairs\n */\n fromPairs(): LoDashExplicitObjectWrapper;\n }\n\n //_.head\n interface LoDashStatic {\n /**\n * Gets the first element of array.\n *\n * @alias _.first\n *\n * @param array The array to query.\n * @return Returns the first element of array.\n */\n head(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.head\n */\n head(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n head(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n head(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.head\n */\n head(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n head(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n head(): T;\n }\n\n //_.indexOf\n interface LoDashStatic {\n /**\n * Gets the index at which the first occurrence of `value` is found in `array`\n * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons. If `fromIndex` is negative, it's used as the offset\n * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`\n * performs a faster binary search.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @param {number} [fromIndex=0] The index to search from.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.indexOf([1, 2, 1, 2], 2);\n * // => 1\n *\n * // using `fromIndex`\n * _.indexOf([1, 2, 1, 2], 2, 2);\n * // => 3\n */\n indexOf(\n array: List | null | undefined,\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: TValue,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: T,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: TValue,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n //_.intersectionBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.intersection` except that it accepts `iteratee`\n * which is invoked for each element of each `arrays` to generate the criterion\n * by which uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new array of shared values.\n * @example\n *\n * _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);\n * // => [2.1]\n *\n * // using the `_.property` iteratee shorthand\n * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 1 }]\n */\n intersectionBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.intersectionWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.intersection` except that it accepts `comparator`\n * which is invoked to compare elements of `arrays`. The comparator is invoked\n * with two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of shared values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.intersectionWith(objects, others, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }]\n */\n intersectionWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.join\n interface LoDashStatic {\n /**\n * Converts all elements in `array` into a string separated by `separator`.\n *\n * @param array The array to convert.\n * @param separator The element separator.\n * @returns Returns the joined string.\n */\n join(\n array: List | null | undefined,\n separator?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n //_.pullAll DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.pull` except that it accepts an array of values to remove.\n *\n * **Note:** Unlike `_.difference`, this method mutates `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to modify.\n * @param {Array} values The values to remove.\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [1, 2, 3, 1, 2, 3];\n *\n * _.pull(array, [2, 3]);\n * console.log(array);\n * // => [1, 1]\n */\n pullAll(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.pullAllBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.pullAll` except that it accepts `iteratee` which is\n * invoked for each element of `array` and `values` to to generate the criterion\n * by which uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * **Note:** Unlike `_.differenceBy`, this method mutates `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to modify.\n * @param {Array} values The values to remove.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];\n *\n * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');\n * console.log(array);\n * // => [{ 'x': 2 }]\n */\n pullAllBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.reverse DUMMY\n interface LoDashStatic {\n /**\n * Reverses `array` so that the first element becomes the last, the second\n * element becomes the second to last, and so on.\n *\n * **Note:** This method mutates `array` and is based on\n * [`Array#reverse`](https://mdn.io/Array/reverse).\n *\n * @memberOf _\n * @category Array\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [1, 2, 3];\n *\n * _.reverse(array);\n * // => [3, 2, 1]\n *\n * console.log(array);\n * // => [3, 2, 1]\n */\n reverse(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.sortedIndexOf\n interface LoDashStatic {\n /**\n * This method is like `_.indexOf` except that it performs a binary\n * search on a sorted `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.sortedIndexOf([1, 1, 2, 2], 2);\n * // => 2\n */\n sortedIndexOf(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: TValue\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: TValue\n ): LoDashExplicitWrapper;\n }\n\n //_.initial\n interface LoDashStatic {\n /**\n * Gets all but the last element of array.\n *\n * @param array The array to query.\n * @return Returns the slice of array.\n */\n initial(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashExplicitArrayWrapper;\n }\n\n //_.intersection\n interface LoDashStatic {\n /**\n * Creates an array of unique values that are included in all of the provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of shared values.\n */\n intersection(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.last\n interface LoDashStatic {\n /**\n * Gets the last element of array.\n *\n * @param array The array to query.\n * @return Returns the last element of array.\n */\n last(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.last\n */\n last(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.last\n */\n last(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.last\n */\n last(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.last\n */\n last(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.last\n */\n last(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.last\n */\n last(): T;\n }\n\n //_.lastIndexOf\n interface LoDashStatic {\n /**\n * This method is like _.indexOf except that it iterates over elements of array from right to left.\n *\n * @param array The array to search.\n * @param value The value to search for.\n * @param fromIndex The index to search from or true to perform a binary search on a sorted array.\n * @return Returns the index of the matched value, else -1.\n */\n lastIndexOf(\n array: List | null | undefined,\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: TResult,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: T,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: TResult,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n //_.nth\n interface LoDashStatic {\n /**\n * Gets the element at index `n` of `array`. If `n` is negative, the nth element from the end is returned.\n *\n * @param array array The array to query.\n * @param value The index of the element to return.\n * @return Returns the nth element of `array`.\n */\n nth(\n array: List | null | undefined,\n n?: number\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?: number\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): TResult | undefined;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): LoDashExplicitWrapper;\n }\n\n //_.pull\n interface LoDashStatic {\n /**\n * Removes all provided values from array using SameValueZero for equality comparisons.\n *\n * Note: Unlike _.without, this method mutates array.\n *\n * @param array The array to modify.\n * @param values The values to remove.\n * @return Returns array.\n */\n pull(\n array: T[],\n ...values: T[]\n ): T[];\n\n /**\n * @see _.pull\n */\n pull(\n array: List,\n ...values: T[]\n ): List;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.pull\n */\n pull(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.pull\n */\n pull(...values: TValue[]): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.pull\n */\n pull(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.pull\n */\n pull(...values: TValue[]): LoDashExplicitObjectWrapper>;\n }\n\n //_.pullAt\n interface LoDashStatic {\n /**\n * Removes elements from array corresponding to the given indexes and returns an array of the removed elements.\n * Indexes may be specified as an array of indexes or as individual arguments.\n *\n * Note: Unlike _.at, this method mutates array.\n *\n * @param array The array to modify.\n * @param indexes The indexes of elements to remove, specified as individual indexes or arrays of indexes.\n * @return Returns the new array of removed elements.\n */\n pullAt(\n array: List,\n ...indexes: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.remove\n interface LoDashStatic {\n /**\n * Removes all elements from array that predicate returns truthy for and returns an array of the removed\n * elements. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * Note: Unlike _.filter, this method mutates array.\n *\n * @param array The array to modify.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new array of removed elements.\n */\n remove(\n array: List,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.remove\n */\n remove(\n array: List,\n predicate?: string\n ): T[];\n\n /**\n * @see _.remove\n */\n remove(\n array: List,\n predicate?: W\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.tail\n interface LoDashStatic {\n /**\n * Gets all but the first element of array.\n *\n * @alias _.tail\n *\n * @param array The array to query.\n * @return Returns the slice of array.\n */\n tail(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n //_.slice\n interface LoDashStatic {\n /**\n * Creates a slice of array from start up to, but not including, end.\n *\n * @param array The array to slice.\n * @param start The start position.\n * @param end The end position.\n * @return Returns the slice of array.\n */\n slice(\n array: T[] | null | undefined,\n start?: number,\n end?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.slice\n */\n slice(\n start?: number,\n end?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.slice\n */\n slice(\n start?: number,\n end?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedIndex\n interface LoDashStatic {\n /**\n * Uses a binary search to determine the lowest index at which `value` should\n * be inserted into `array` in order to maintain its sort order.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * _.sortedIndex([30, 50], 40);\n * // => 1\n *\n * _.sortedIndex([4, 5], 4);\n * // => 0\n */\n sortedIndex(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n // _.sortedIndexBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortedIndex` except that it accepts `iteratee`\n * which is invoked for `value` and each element of `array` to compute their\n * sort ranking. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };\n *\n * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));\n * // => 1\n *\n * // using the `_.property` iteratee shorthand\n * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');\n * // => 0\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: Object\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndex\n interface LoDashStatic {\n /**\n * This method is like `_.sortedIndex` except that it returns the highest\n * index at which `value` should be inserted into `array` in order to\n * maintain its sort order.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * _.sortedLastIndex([4, 5], 4);\n * // => 1\n */\n sortedLastIndex(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndexBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortedLastIndex` except that it accepts `iteratee`\n * which is invoked for `value` and each element of `array` to compute their\n * sort ranking. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * // using the `_.property` iteratee shorthand\n * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');\n * // => 1\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: Object\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndexOf DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.lastIndexOf` except that it performs a binary\n * search on a sorted `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.sortedLastIndexOf([1, 1, 2, 2], 2);\n * // => 3\n */\n sortedLastIndexOf(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n //_.tail\n interface LoDashStatic {\n /**\n * @see _.rest\n */\n tail(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n //_.take\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements taken from the beginning.\n *\n * @param array The array to query.\n * @param n The number of elements to take.\n * @return Returns the slice of array.\n */\n take(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.takeRight\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements taken from the end.\n *\n * @param array The array to query.\n * @param n The number of elements to take.\n * @return Returns the slice of array.\n */\n takeRight(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.takeRightWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array with elements taken from the end. Elements are taken until predicate returns\n * falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.takeWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array with elements taken from the beginning. Elements are taken until predicate returns\n * falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.union\n interface LoDashStatic {\n /**\n * Creates an array of unique values, in order, from all of the provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of combined values.\n */\n union(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n //_.unionBy\n interface LoDashStatic {\n /**\n * This method is like `_.union` except that it accepts `iteratee` which is\n * invoked for each element of each `arrays` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @param arrays The arrays to inspect.\n * @param iteratee The iteratee invoked per element.\n * @return Returns the new array of combined values.\n */\n unionBy(\n arrays: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays: List | null | undefined,\n ...iteratee: any[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.uniq\n interface LoDashStatic {\n /**\n * Creates a duplicate-free version of an array, using\n * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons, in which only the first occurrence of each element\n * is kept.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.uniq([2, 1, 2]);\n * // => [2, 1]\n */\n uniq(\n array: List | null | undefined\n ): T[];\n\n /**\n * @see _.uniq\n */\n uniq(\n array: List | null | undefined\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n uniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n //_.uniqBy\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.uniqBy([2.1, 1.2, 2.3], Math.floor);\n * // => [2.1, 1.2]\n *\n * // using the `_.property` iteratee shorthand\n * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 1 }, { 'x': 2 }]\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: Object\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: TWhere\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: Object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: Object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedUniq\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it's designed and optimized\n * for sorted arrays.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.sortedUniq([1, 1, 2]);\n * // => [1, 2]\n */\n sortedUniq(\n array: List | null | undefined\n ): T[];\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(\n array: List | null | undefined\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n sortedUniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedUniqBy\n interface LoDashStatic {\n /**\n * This method is like `_.uniqBy` except that it's designed and optimized\n * for sorted arrays.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function} [iteratee] The iteratee invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);\n * // => [1.1, 2.2]\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: Object\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: TWhere\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: Object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: Object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.unionWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.union` except that it accepts `comparator` which\n * is invoked to compare elements of `arrays`. The comparator is invoked\n * with two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of combined values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.unionWith(objects, others, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]\n */\n unionWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.uniqWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it accepts `comparator` which\n * is invoked to compare elements of `array`. The comparator is invoked with\n * two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.uniqWith(objects, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]\n */\n uniqWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.unzip\n interface LoDashStatic {\n /**\n * This method is like _.zip except that it accepts an array of grouped elements and creates an array\n * regrouping the elements to their pre-zip configuration.\n *\n * @param array The array of grouped elements to process.\n * @return Returns the new array of regrouped elements.\n */\n unzip(array: List> | null | undefined): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashExplicitArrayWrapper;\n }\n\n //_.unzipWith\n interface LoDashStatic {\n /**\n * This method is like _.unzip except that it accepts an iteratee to specify how regrouped values should be\n * combined. The iteratee is bound to thisArg and invoked with four arguments: (accumulator, value, index,\n * group).\n *\n * @param array The array of grouped elements to process.\n * @param iteratee The function to combine regrouped values.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new array of regrouped elements.\n */\n unzipWith(\n array: List> | null | undefined,\n iteratee?: MemoIterator\n ): TResult[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unzipWith\n */\n unzipWith(\n iteratee?: MemoIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unzipWith\n */\n unzipWith(\n iteratee?: MemoIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n //_.without\n interface LoDashStatic {\n /**\n * Creates an array excluding all provided values using SameValueZero for equality comparisons.\n *\n * @param array The array to filter.\n * @param values The values to exclude.\n * @return Returns the new array of filtered values.\n */\n without(\n array: List | null | undefined,\n ...values: T[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n //_.xor\n interface LoDashStatic {\n /**\n * Creates an array of unique values that is the symmetric difference of the provided arrays.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of values.\n */\n xor(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n //_.xorBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.xor` except that it accepts `iteratee` which is\n * invoked for each element of each `arrays` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new array of values.\n * @example\n *\n * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);\n * // => [1.2, 4.3]\n *\n * // using the `_.property` iteratee shorthand\n * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 2 }]\n */\n xorBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.xorWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.xor` except that it accepts `comparator` which is\n * invoked to compare elements of `arrays`. The comparator is invoked with\n * two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.xorWith(objects, others, _.isEqual);\n * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]\n */\n xorWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.zip\n interface LoDashStatic {\n /**\n * Creates an array of grouped elements, the first of which contains the first elements of the given arrays,\n * the second of which contains the second elements of the given arrays, and so on.\n *\n * @param arrays The arrays to process.\n * @return Returns the new array of grouped elements.\n */\n zip(...arrays: Array | null | undefined>): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashExplicitArrayWrapper;\n }\n\n //_.zipObject\n interface LoDashStatic {\n /**\n * This method is like _.fromPairs except that it accepts two arrays, one of property\n * identifiers and one of corresponding values.\n *\n * @param props The property names.\n * @param values The property values.\n * @return Returns the new object.\n */\n zipObject(\n props: List|List>,\n values?: List\n ): TResult;\n /**\n * This method is like _.zipObject except that it supports property paths.\n *\n * @param props The property names.\n * @param values The property values.\n * @return Returns the new object.\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): TResult;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n props: List|List>,\n values?: List\n ): TResult;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): TResult;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n props: List|List>,\n values?: List\n ): _.Dictionary;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): _.Dictionary;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n }\n\n //_.zipWith\n interface LoDashStatic {\n /**\n * This method is like _.zip except that it accepts an iteratee to specify how grouped values should be\n * combined. The iteratee is bound to thisArg and invoked with four arguments: (accumulator, value, index,\n * group).\n * @param {...Array} [arrays] The arrays to process.\n * @param {Function} [iteratee] The function to combine grouped values.\n * @param {*} [thisArg] The `this` binding of `iteratee`.\n * @return Returns the new array of grouped elements.\n */\n zipWith(...args: any[]): TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.zipWith\n */\n zipWith(...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n /*********\n * Chain *\n *********/\n\n //_.chain\n interface LoDashStatic {\n /**\n * Creates a lodash object that wraps value with explicit method chaining enabled.\n *\n * @param value The value to wrap.\n * @return Returns the new lodash wrapper instance.\n */\n chain(value: number): LoDashExplicitWrapper;\n chain(value: string): LoDashExplicitWrapper;\n chain(value: boolean): LoDashExplicitWrapper;\n chain(value: null | undefined): LoDashExplicitWrapper;\n chain(value: T[]): LoDashExplicitArrayWrapper;\n chain(value: ReadonlyArray): LoDashExplicitArrayWrapper;\n chain(value: T[] | null | undefined): LoDashExplicitNillableArrayWrapper;\n chain(value: ReadonlyArray | null | undefined): LoDashExplicitNillableArrayWrapper;\n chain(value: T): LoDashExplicitObjectWrapper;\n chain(value: T | null | undefined): LoDashExplicitObjectWrapper;\n chain(value: any): LoDashExplicitWrapper;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashImplicitNillableArrayWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitNillableArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashImplicitNillableObjectWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitNillableObjectWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.chain\n */\n chain(): TWrapper;\n }\n\n //_.tap\n interface LoDashStatic {\n /**\n * This method invokes interceptor and returns value. The interceptor is bound to thisArg and invoked with one\n * argument; (value). The purpose of this method is to \"tap into\" a method chain in order to perform operations\n * on intermediate results within the chain.\n *\n * @param value The value to provide to interceptor.\n * @param interceptor The function to invoke.\n * @parem thisArg The this binding of interceptor.\n * @return Returns value.\n **/\n tap(\n value: T,\n interceptor: (value: T) => void\n ): T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.tap\n */\n tap(\n interceptor: (value: T) => void\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.tap\n */\n tap(\n interceptor: (value: T) => void\n ): TWrapper;\n }\n\n //_.thru\n interface LoDashStatic {\n /**\n * This method is like _.tap except that it returns the result of interceptor.\n *\n * @param value The value to provide to interceptor.\n * @param interceptor The function to invoke.\n * @param thisArg The this binding of interceptor.\n * @return Returns the result of interceptor.\n */\n thru(\n value: T,\n interceptor: (value: T) => TResult\n ): TResult;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.commit\n interface LoDashImplicitWrapperBase {\n /**\n * Executes the chained sequence and returns the wrapped result.\n *\n * @return Returns the new lodash wrapper instance.\n */\n commit(): TWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.commit\n */\n commit(): TWrapper;\n }\n\n //_.prototype.concat\n interface LoDashImplicitWrapperBase {\n /**\n * Creates a new array joining a wrapped array with any additional arrays and/or values.\n *\n * @param items\n * @return Returns the new concatenated array.\n */\n concat(...items: Array>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.plant\n interface LoDashImplicitWrapperBase {\n /**\n * Creates a clone of the chained sequence planting value as the wrapped value.\n * @param value The value to plant as the wrapped value.\n * @return Returns the new lodash wrapper instance.\n */\n plant(value: number): LoDashImplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: string): LoDashImplicitStringWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: boolean): LoDashImplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: number[]): LoDashImplicitNumberArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: ReadonlyArray): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: any): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.plant\n */\n plant(value: number): LoDashExplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: string): LoDashExplicitStringWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: boolean): LoDashExplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: number[]): LoDashExplicitNumberArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: ReadonlyArray): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: any): LoDashExplicitWrapper;\n }\n\n //_.prototype.reverse\n interface LoDashImplicitArrayWrapper {\n /**\n * Reverses the wrapped array so the first element becomes the last, the second element becomes the second to\n * last, and so on.\n *\n * Note: This method mutates the wrapped array.\n *\n * @return Returns the new reversed lodash wrapper instance.\n */\n reverse(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.reverse\n */\n reverse(): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.toJSON\n interface LoDashWrapperBase {\n /**\n * @see _.value\n */\n toJSON(): T;\n }\n\n //_.prototype.toString\n interface LoDashWrapperBase {\n /**\n * Produces the result of coercing the unwrapped value to a string.\n *\n * @return Returns the coerced string value.\n */\n toString(): string;\n }\n\n //_.prototype.value\n interface LoDashWrapperBase {\n /**\n * Executes the chained sequence to extract the unwrapped value.\n *\n * @alias _.toJSON, _.valueOf\n *\n * @return Returns the resolved unwrapped value.\n */\n value(): T;\n }\n\n //_.valueOf\n interface LoDashWrapperBase {\n /**\n * @see _.value\n */\n valueOf(): T;\n }\n\n /**************\n * Collection *\n **************/\n\n //_.at\n interface LoDashStatic {\n /**\n * Creates an array of elements corresponding to the given keys, or indexes, of collection. Keys may be\n * specified as individual arguments or as arrays of keys.\n *\n * @param collection The collection to iterate over.\n * @param props The property names or indexes of elements to pick, specified individually or in arrays.\n * @return Returns the new array of picked elements.\n */\n at(\n collection: List|Dictionary | null | undefined,\n ...props: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.countBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is the number of times the key was returned by iteratee. The\n * iteratee is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n countBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: W\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.each\n interface LoDashStatic {\n each: typeof _.forEach;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEach\n */\n each(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEach\n */\n each(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.eachRight\n interface LoDashStatic {\n eachRight: typeof _.forEachRight;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.every\n interface LoDashStatic {\n /**\n * Checks if predicate returns truthy for all elements of collection. Iteration is stopped once predicate\n * returns falsey. The predicate is invoked with three arguments: (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @return Returns true if all elements pass the predicate check, else false.\n */\n every(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: NumericDictionary | null | undefined,\n predicate?: NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: string|any[]|PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n //_.filter\n interface LoDashStatic {\n /**\n * Iterates over elements of collection, returning an array of all elements predicate returns truthy for. The\n * predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new filtered array.\n */\n filter(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard\n ): S[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard\n ): S[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: string | null | undefined,\n predicate?: StringIterator\n ): string[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: List|Dictionary | null | undefined,\n predicate: string | [string, any] | RegExp | PartialObject\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.filter\n */\n filter(\n predicate?: StringIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | DictionaryIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.filter\n */\n filter(\n predicate?: StringIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | DictionaryIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.find\n interface LoDashStatic {\n /**\n * Iterates over elements of collection, returning the first element predicate returns truthy for.\n * The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the matched element, else undefined.\n */\n find(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n \n /**\n * @see _.find\n */\n find(\n collection: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: List|Dictionary | null | undefined,\n predicate?: string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | DictionaryIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): TResult|undefined;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): any;\n }\n\n //_.findLast\n interface LoDashStatic {\n /**\n * This method is like _.find except that it iterates over elements of a collection from\n * right to left.\n * @param collection Searches for a value in this list.\n * @param predicate The function called per iteration.\n * @param fromIndex The index to search from.\n * @return The found element, else undefined.\n **/\n findLast(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n \n /**\n * @see _.findLast\n */\n findLast(\n collection: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: List|Dictionary | null | undefined,\n predicate?: string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | DictionaryIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): TResult|undefined;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): any;\n }\n\n //_.flatMap\n interface LoDashStatic {\n /**\n * Creates an array of flattened values by running each element in collection through iteratee\n * and concating its result to the other mapped values. The iteratee is invoked with three arguments:\n * (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @return Returns the new flattened array.\n */\n flatMap(\n collection: List> | Dictionary> | NumericDictionary> | null | undefined\n ): T[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: List | null | undefined,\n iteratee: ListIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: Dictionary | null | undefined,\n iteratee: DictionaryIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: NumericDictionary | null | undefined,\n iteratee: NumericDictionaryIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: object | null | undefined,\n iteratee?: ObjectIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: object | null | undefined,\n iteratee: object\n ): boolean[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: StringIterator>\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|DictionaryIterator>|NumericDictionaryIterator>\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ObjectIterator>|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: StringIterator>\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|DictionaryIterator>|NumericDictionaryIterator>\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ObjectIterator>|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n //_.forEach\n interface LoDashStatic {\n /**\n * Iterates over elements of collection invoking iteratee for each element. The iteratee is bound to thisArg\n * and invoked with three arguments:\n * (value, index|key, collection). Iteratee functions may exit iteration early by explicitly returning false.\n *\n * Note: As with other \"Collections\" methods, objects with a \"length\" property are iterated like arrays. To\n * avoid this behavior _.forIn or _.forOwn may be used for object iteration.\n *\n * @alias _.each\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n */\n forEach(\n collection: TString,\n iteratee?: StringIterator\n ): TString;\n\n /**\n * @see _.forEach\n */\n forEach | null | undefined>(\n collection: TList & (List | null | undefined),\n iteratee?: ListIterator\n ): TList;\n\n /**\n * @see _.forEach\n */\n forEach | null | undefined>(\n collection: TDictionary & (Dictionary | null | undefined),\n iteratee?: DictionaryIterator\n ): TDictionary;\n\n /**\n * @see _.forEach\n */\n forEach(\n collection: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.forEachRight\n interface LoDashStatic {\n /**\n * This method is like _.forEach except that it iterates over elements of collection from right to left.\n *\n * @alias _.eachRight\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function called per iteration.\n * @param thisArg The this binding of callback.\n */\n forEachRight(\n collection: TString,\n iteratee?: StringIterator\n ): TString;\n\n /**\n * @see _.forEachRight\n */\n forEachRight | null | undefined>(\n collection: TList & (List | null | undefined),\n iteratee?: ListIterator\n ): TList;\n\n /**\n * @see _.forEachRight\n */\n forEachRight | null | undefined>(\n collection: TDictionary & (Dictionary | null | undefined),\n iteratee?: DictionaryIterator\n ): TDictionary;\n\n /**\n * @see _.forEachRight\n */\n forEachRight(\n collection: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.groupBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is an array of the elements responsible for generating the\n * key. The iteratee is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n groupBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: TWhere\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: Object\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: Object\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.includes\n interface LoDashStatic {\n /**\n * Checks if target is in collection using SameValueZero for equality comparisons. If fromIndex is negative,\n * it’s used as the offset from the end of collection.\n *\n * @param collection The collection to search.\n * @param target The value to search for.\n * @param fromIndex The index to search from.\n * @return True if the target element is found, else false.\n */\n includes(\n collection: List|Dictionary | null | undefined,\n target: T,\n fromIndex?: number\n ): boolean;\n\n /**\n * @see _.includes\n */\n includes(\n collection: string | null | undefined,\n target: string,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: T,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: TValue,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.includes\n */\n includes(\n target: string,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: T,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: TValue,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.includes\n */\n includes(\n target: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.keyBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is the last element responsible for generating the key. The\n * iteratee function is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n keyBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: W\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator|NumericDictionaryIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: Object\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator|NumericDictionaryIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: Object\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.invoke\n interface LoDashStatic {\n /**\n * Invokes the method at path of object.\n * @param object The object to query.\n * @param path The path of the method to invoke.\n * @param args The arguments to invoke the method with.\n **/\n invoke(\n object: TObject,\n path: Many,\n ...args: any[]): TResult;\n\n /**\n * @see _.invoke\n **/\n invoke(\n object: Dictionary|TValue[],\n path: Many,\n ...args: any[]): TResult;\n\n /**\n * @see _.invoke\n **/\n invoke(\n object: any,\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n //_.invokeMap\n interface LoDashStatic {\n /**\n * Invokes the method named by methodName on each element in the collection returning\n * an array of the results of each invoked method. Additional arguments will be provided\n * to each invoked method. If methodName is a function it will be invoked for, and this\n * bound to, each element in the collection.\n * @param collection The collection to iterate over.\n * @param methodName The name of the method to invoke.\n * @param args Arguments to invoke the method with.\n **/\n invokeMap(\n collection: TValue[] | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Array<{}> | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary<{}> | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: TValue[] | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Array<{}> | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary<{}> | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashExplicitArrayWrapper;\n }\n\n //_.map\n interface LoDashStatic {\n /**\n * Creates an array of values by running each element in collection through iteratee. The iteratee is bound to\n * thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property value\n * of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * Many lodash methods are guarded to work as iteratees for methods like _.every, _.filter, _.map, _.mapValues,\n * _.reject, and _.some.\n *\n * The guarded methods are:\n * ary, callback, chunk, clone, create, curry, curryRight, drop, dropRight, every, fill, flatten, invert, max,\n * min, parseInt, slice, sortBy, take, takeRight, template, trim, trimLeft, trimRight, trunc, random, range,\n * sample, some, sum, uniq, and words\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new mapped array.\n */\n map(\n collection: List | null | undefined,\n iteratee: ListIterator\n ): TResult[];\n\n /**\n * @see _.map\n */\n map(collection: List | null | undefined): T[];\n\n /**\n * @see _.map\n */\n map(\n collection: Dictionary | null | undefined,\n iteratee: DictionaryIterator\n ): TResult[];\n\n /** @see _.map */\n map(\n collection: Dictionary | null | undefined,\n iteratee: K\n ): T[K][];\n\n /** @see _.map */\n map(collection: Dictionary | null | undefined): T[];\n\n map(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): TResult[];\n\n /** @see _.map */\n map(collection: List | null | undefined, iteratee: K): T[K][];\n\n /**\n * @see _.map\n */\n map(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: string\n ): TResult[];\n\n /**\n * @see _.map\n */\n map(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: TObject\n ): boolean[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.partition\n interface LoDashStatic {\n /**\n * Creates an array of elements split into two groups, the first of which contains elements predicate returns truthy for,\n * while the second of which contains elements predicate returns falsey for.\n * The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback\n * returns the property value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback\n * returns true for elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns\n * true for elements that have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the array of grouped elements.\n **/\n partition(\n collection: List | null | undefined,\n callback: ListIterator): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n callback: DictionaryIterator): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n whereValue: W): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n whereValue: W): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n path: string,\n srcValue: any): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n path: string,\n srcValue: any): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n pluckValue: string): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n pluckValue: string): T[][];\n }\n\n interface LoDashImplicitStringWrapper {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n whereValue: W): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n path: string,\n srcValue: any): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n pluckValue: string): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n callback: DictionaryIterator): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n path: string,\n srcValue: any): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n pluckValue: string): LoDashImplicitArrayWrapper;\n }\n\n //_.reduce\n interface LoDashStatic {\n /**\n * Reduces a collection to a value which is the accumulated result of running each\n * element in the collection through the callback, where each successive callback execution\n * consumes the return value of the previous execution. If accumulator is not provided the\n * first element of the collection will be used as the initial accumulator value. The callback\n * is bound to thisArg and invoked with four arguments; (accumulator, value, index|key, collection).\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param accumulator Initial value of the accumulator.\n * @param thisArg The this binding of callback.\n * @return Returns the accumulated value.\n **/\n reduce(\n collection: T[] | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: List | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: Dictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: NumericDictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: T[] | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: List | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: Dictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: NumericDictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**LoDashExplicitWrapper\n * @see _.reduce\n */\n reduce(\n callback: MemoIterator,\n accumulator: TResult): LoDashExplicitWrapper;\n\n /**\n * @see _.reduce\n */\n reduce(\n callback: MemoIterator): LoDashExplicitWrapper;\n }\n\n //_.reduceRight\n interface LoDashStatic {\n /**\n * This method is like _.reduce except that it iterates over elements of a collection from\n * right to left.\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param accumulator Initial value of the accumulator.\n * @param thisArg The this binding of callback.\n * @return The accumulated value.\n **/\n reduceRight(\n collection: T[] | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: List | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: Dictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: T[] | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: List | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: Dictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n }\n\n //_.reject\n interface LoDashStatic {\n /**\n * The opposite of _.filter; this method returns the elements of collection that predicate does not return\n * truthy for.\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new filtered array.\n */\n reject(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: string | null | undefined,\n predicate?: StringIterator\n ): string[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: List|Dictionary | null | undefined,\n predicate: string\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: List|Dictionary | null | undefined,\n predicate: W\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.reject\n */\n reject(\n predicate?: StringIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.reject\n */\n reject(\n predicate?: StringIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashExplicitArrayWrapper;\n }\n\n //_.sample\n interface LoDashStatic {\n /**\n * Gets a random element from collection.\n *\n * @param collection The collection to sample.\n * @return Returns the random element.\n */\n sample(\n collection: List | Dictionary | NumericDictionary | object | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sample\n */\n sample(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sample\n */\n sample(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sample\n */\n sample(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sample\n */\n sample(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sample\n */\n sample(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sample\n */\n sample(): TWrapper;\n }\n\n //_.sampleSize\n interface LoDashStatic {\n /**\n * Gets n random elements at unique keys from collection up to the size of collection.\n *\n * @param collection The collection to sample.\n * @param n The number of elements to sample.\n * @return Returns the random elements.\n */\n sampleSize(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n n?: number\n ): T[];\n\n /**\n * @see _.sampleSize\n */\n sampleSize(\n collection: O | null | undefined,\n n?: number\n ): T[];\n\n /**\n * @see _.sampleSize\n */\n sampleSize(\n collection: Object | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.shuffle\n interface LoDashStatic {\n /**\n * Creates an array of shuffled values, using a version of the Fisher-Yates shuffle.\n *\n * @param collection The collection to shuffle.\n * @return Returns the new shuffled array.\n */\n shuffle(collection: List|Dictionary | null | undefined): T[];\n\n /**\n * @see _.shuffle\n */\n shuffle(collection: string | null | undefined): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n //_.size\n interface LoDashStatic {\n /**\n * Gets the size of collection by returning its length for array-like values or the number of own enumerable\n * properties for objects.\n *\n * @param collection The collection to inspect.\n * @return Returns the size of collection.\n */\n size(collection: List|Dictionary | null | undefined): number;\n\n /**\n * @see _.size\n */\n size(collection: string | null | undefined): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n //_.some\n interface LoDashStatic {\n /**\n * Checks if predicate returns truthy for any element of collection. Iteration is stopped once predicate\n * returns truthy. The predicate is invoked with three arguments: (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @return Returns true if any element passes the predicate check, else false.\n */\n some(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: NumericDictionary | null | undefined,\n predicate?: NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: ObjectIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: PartialObject\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: PartialObject\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator|ObjectIterator\n ): boolean;\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator|ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n //_.sortBy\n interface LoDashStatic {\n /**\n * Creates an array of elements, sorted in ascending order by the results of\n * running each element in a collection through each iteratee. This method\n * performs a stable sort, that is, it preserves the original sort order of\n * equal elements. The iteratees are invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Collection\n * @param {Array|Object} collection The collection to iterate over.\n * @param {...(Function|Function[]|Object|Object[]|string|string[])} [iteratees=[_.identity]]\n * The iteratees to sort by, specified individually or in arrays.\n * @returns {Array} Returns the new sorted array.\n * @example\n *\n * var users = [\n * { 'user': 'fred', 'age': 48 },\n * { 'user': 'barney', 'age': 36 },\n * { 'user': 'fred', 'age': 42 },\n * { 'user': 'barney', 'age': 34 }\n * ];\n *\n * _.sortBy(users, function(o) { return o.user; });\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n *\n * _.sortBy(users, ['user', 'age']);\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]\n *\n * _.sortBy(users, 'user', function(o) {\n * return Math.floor(o.age / 10);\n * });\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n */\n sortBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined,\n whereValue: W\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List | null | undefined,\n iteratees: Array|string|Object>\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List | null | undefined,\n ...iteratees: Array|Object|string>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(...iteratees: Array|Object|string>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n **/\n sortBy(iteratees: Array|string|Object>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashExplicitArrayWrapper;\n }\n\n //_.orderBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortBy` except that it allows specifying the sort\n * orders of the iteratees to sort by. If `orders` is unspecified, all values\n * are sorted in ascending order. Otherwise, specify an order of \"desc\" for\n * descending or \"asc\" for ascending sort order of corresponding values.\n *\n * @static\n * @memberOf _\n * @category Collection\n * @param {Array|Object} collection The collection to iterate over.\n * @param {Function[]|Object[]|string[]} [iteratees=[_.identity]] The iteratees to sort by.\n * @param {string[]} [orders] The sort orders of `iteratees`.\n * @param- {Object} [guard] Enables use as an iteratee for functions like `_.reduce`.\n * @returns {Array} Returns the new sorted array.\n * @example\n *\n * var users = [\n * { 'user': 'fred', 'age': 48 },\n * { 'user': 'barney', 'age': 34 },\n * { 'user': 'fred', 'age': 42 },\n * { 'user': 'barney', 'age': 36 }\n * ];\n *\n * // sort by `user` in ascending order and by `age` in descending order\n * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n */\n orderBy(\n collection: List | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: List | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: NumericDictionary | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: NumericDictionary | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: Dictionary | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: Dictionary | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W|(ListIterator|string|W)>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n /********\n * Date *\n ********/\n\n //_.now\n interface LoDashStatic {\n /**\n * Gets the number of milliseconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC).\n *\n * @return The number of milliseconds.\n */\n now(): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.now\n */\n now(): number;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.now\n */\n now(): LoDashExplicitWrapper;\n }\n\n /*************\n * Functions *\n *************/\n\n //_.after\n interface LoDashStatic {\n /**\n * The opposite of _.before; this method creates a function that invokes func once it’s called n or more times.\n *\n * @param n The number of calls before func is invoked.\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n after(\n n: number,\n func: TFunc\n ): TFunc;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.after\n **/\n after(func: TFunc): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.after\n **/\n after(func: TFunc): LoDashExplicitObjectWrapper;\n }\n\n //_.ary\n interface LoDashStatic {\n /**\n * Creates a function that accepts up to n arguments ignoring any additional arguments.\n *\n * @param func The function to cap arguments for.\n * @param n The arity cap.\n * @returns Returns the new function.\n */\n ary(\n func: Function,\n n?: number\n ): TResult;\n\n ary(\n func: T,\n n?: number\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.ary\n */\n ary(n?: number): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.ary\n */\n ary(n?: number): LoDashExplicitObjectWrapper;\n }\n\n //_.before\n interface LoDashStatic {\n /**\n * Creates a function that invokes func, with the this binding and arguments of the created function, while\n * it’s called less than n times. Subsequent calls to the created function return the result of the last func\n * invocation.\n *\n * @param n The number of calls at which func is no longer invoked.\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n before(\n n: number,\n func: TFunc\n ): TFunc;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.before\n **/\n before(func: TFunc): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.before\n **/\n before(func: TFunc): LoDashExplicitObjectWrapper;\n }\n\n //_.bind\n interface FunctionBind {\n placeholder: any;\n\n (\n func: T,\n thisArg: any,\n ...partials: any[]\n ): TResult;\n\n (\n func: Function,\n thisArg: any,\n ...partials: any[]\n ): TResult;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of thisArg and prepends any additional _.bind\n * arguments to those provided to the bound function.\n *\n * The _.bind.placeholder value, which defaults to _ in monolithic builds, may be used as a placeholder for\n * partially applied arguments.\n *\n * Note: Unlike native Function#bind this method does not set the \"length\" property of bound functions.\n *\n * @param func The function to bind.\n * @param thisArg The this binding of func.\n * @param partials The arguments to be partially applied.\n * @return Returns the new bound function.\n */\n bind: FunctionBind;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bind\n */\n bind(\n thisArg: any,\n ...partials: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bind\n */\n bind(\n thisArg: any,\n ...partials: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.bindAll\n interface LoDashStatic {\n /**\n * Binds methods of an object to the object itself, overwriting the existing method. Method names may be\n * specified as individual arguments or as arrays of method names. If no method names are provided all\n * enumerable function properties, own and inherited, of object are bound.\n *\n * Note: This method does not set the \"length\" property of bound functions.\n *\n * @param object The object to bind and assign the bound methods to.\n * @param methodNames The object method names to bind, specified as individual method names or arrays of\n * method names.\n * @return Returns object.\n */\n bindAll(\n object: T,\n ...methodNames: Array>\n ): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bindAll\n */\n bindAll(...methodNames: Array>): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bindAll\n */\n bindAll(...methodNames: Array>): LoDashExplicitObjectWrapper;\n }\n\n //_.bindKey\n interface FunctionBindKey {\n placeholder: any;\n\n (\n object: T,\n key: any,\n ...partials: any[]\n ): TResult;\n\n (\n object: Object,\n key: any,\n ...partials: any[]\n ): TResult;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes the method at object[key] and prepends any additional _.bindKey arguments\n * to those provided to the bound function.\n *\n * This method differs from _.bind by allowing bound functions to reference methods that may be redefined\n * or don’t yet exist. See Peter Michaux’s article for more details.\n *\n * The _.bindKey.placeholder value, which defaults to _ in monolithic builds, may be used as a placeholder\n * for partially applied arguments.\n *\n * @param object The object the method belongs to.\n * @param key The key of the method.\n * @param partials The arguments to be partially applied.\n * @return Returns the new bound function.\n */\n bindKey: FunctionBindKey;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bindKey\n */\n bindKey(\n key: any,\n ...partials: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bindKey\n */\n bindKey(\n key: any,\n ...partials: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.createCallback\n interface LoDashStatic {\n /**\n * Produces a callback bound to an optional thisArg. If func is a property name the created\n * callback will return the property value for a given element. If func is an object the created\n * callback will return true for elements that contain the equivalent object properties,\n * otherwise it will return false.\n * @param func The value to convert to a callback.\n * @param thisArg The this binding of the created callback.\n * @param argCount The number of arguments the callback accepts.\n * @return A callback function.\n **/\n createCallback(\n func: string,\n argCount?: number): () => any;\n\n /**\n * @see _.createCallback\n **/\n createCallback(\n func: Dictionary,\n argCount?: number): () => boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.createCallback\n **/\n createCallback(\n argCount?: number): LoDashImplicitObjectWrapper<() => any>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.createCallback\n **/\n createCallback(\n argCount?: number): LoDashImplicitObjectWrapper<() => any>;\n }\n\n //_.curry\n interface LoDashStatic {\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1) => R):\n CurriedFunction1;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2) => R):\n CurriedFunction2;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3) => R):\n CurriedFunction3;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3, t4: T4) => R):\n CurriedFunction4;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R):\n CurriedFunction5;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @param arity The arity of func.\n * @return Returns the new curried function.\n */\n curry(\n func: Function,\n arity?: number): TResult;\n }\n\n interface CurriedFunction1 {\n (): CurriedFunction1;\n (t1: T1): R;\n }\n\n interface CurriedFunction2 {\n (): CurriedFunction2;\n (t1: T1): CurriedFunction1;\n (t1: T1, t2: T2): R;\n }\n\n interface CurriedFunction3 {\n (): CurriedFunction3;\n (t1: T1): CurriedFunction2;\n (t1: T1, t2: T2): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3): R;\n }\n\n interface CurriedFunction4 {\n (): CurriedFunction4;\n (t1: T1): CurriedFunction3;\n (t1: T1, t2: T2): CurriedFunction2;\n (t1: T1, t2: T2, t3: T3): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3, t4: T4): R;\n }\n\n interface CurriedFunction5 {\n (): CurriedFunction5;\n (t1: T1): CurriedFunction4;\n (t1: T1, t2: T2): CurriedFunction3;\n (t1: T1, t2: T2, t3: T3): CurriedFunction2;\n (t1: T1, t2: T2, t3: T3, t4: T4): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): R;\n }\n interface RightCurriedFunction1{\n ():RightCurriedFunction1\n (t1:T1):R\n }\n interface RightCurriedFunction2{\n ():RightCurriedFunction2\n (t2:T2):RightCurriedFunction1\n (t1:T1,t2:T2):R\n }\n interface RightCurriedFunction3{\n ():RightCurriedFunction3\n (t3:T3):RightCurriedFunction2\n (t2:T2,t3:T3):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3):R\n }\n interface RightCurriedFunction4{\n ():RightCurriedFunction4\n (t4:T4):RightCurriedFunction3\n (t3:T3,t4:T4):RightCurriedFunction2\n (t2:T2,t3:T3,t4:T4):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3,t4:T4):R\n }\n interface RightCurriedFunction5{\n ():RightCurriedFunction5\n (t5:T5):RightCurriedFunction4\n (t4:T4,t5:T5):RightCurriedFunction3\n (t3:T3,t4:T4,t5:T5):RightCurriedFunction2\n (t2:T2,t3:T3,t4:T4,t5:T5):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5):R\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.curry\n **/\n curry(arity?: number): LoDashImplicitObjectWrapper;\n }\n\n //_.curryRight\n interface LoDashStatic {\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1) => R):\n RightCurriedFunction1;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2) => R):\n RightCurriedFunction2;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3) => R):\n RightCurriedFunction3;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3, t4: T4) => R):\n RightCurriedFunction4;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R):\n RightCurriedFunction5;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @param arity The arity of func.\n * @return Returns the new curried function.\n */\n curryRight(\n func: Function,\n arity?: number): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.curryRight\n **/\n curryRight(arity?: number): LoDashImplicitObjectWrapper;\n }\n\n //_.debounce\n interface DebounceSettings {\n /**\n * Specify invoking on the leading edge of the timeout.\n */\n leading?: boolean;\n\n /**\n * The maximum time func is allowed to be delayed before it’s invoked.\n */\n maxWait?: number;\n\n /**\n * Specify invoking on the trailing edge of the timeout.\n */\n trailing?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since\n * the last time the debounced function was invoked. The debounced function comes with a cancel method to\n * cancel delayed invocations and a flush method to immediately invoke them. Provide an options object to\n * indicate that func should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent\n * calls to the debounced function return the result of the last func invocation.\n *\n * Note: If leading and trailing options are true, func is invoked on the trailing edge of the timeout only\n * if the the debounced function is invoked more than once during the wait timeout.\n *\n * See David Corbacho’s article for details over the differences between _.debounce and _.throttle.\n *\n * @param func The function to debounce.\n * @param wait The number of milliseconds to delay.\n * @param options The options object.\n * @param options.leading Specify invoking on the leading edge of the timeout.\n * @param options.maxWait The maximum time func is allowed to be delayed before it’s invoked.\n * @param options.trailing Specify invoking on the trailing edge of the timeout.\n * @return Returns the new debounced function.\n */\n debounce(\n func: T,\n wait?: number,\n options?: DebounceSettings\n ): T & Cancelable;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.debounce\n */\n debounce(\n wait?: number,\n options?: DebounceSettings\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.debounce\n */\n debounce(\n wait?: number,\n options?: DebounceSettings\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.defer\n interface LoDashStatic {\n /**\n * Defers invoking the func until the current call stack has cleared. Any additional arguments are provided to\n * func when it’s invoked.\n *\n * @param func The function to defer.\n * @param args The arguments to invoke the function with.\n * @return Returns the timer id.\n */\n defer(\n func: T,\n ...args: any[]\n ): number;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defer\n */\n defer(...args: any[]): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.defer\n */\n defer(...args: any[]): LoDashExplicitWrapper;\n }\n\n //_.delay\n interface LoDashStatic {\n /**\n * Invokes func after wait milliseconds. Any additional arguments are provided to func when it’s invoked.\n *\n * @param func The function to delay.\n * @param wait The number of milliseconds to delay invocation.\n * @param args The arguments to invoke the function with.\n * @return Returns the timer id.\n */\n delay(\n func: T,\n wait: number,\n ...args: any[]\n ): number;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.delay\n */\n delay(\n wait: number,\n ...args: any[]\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.delay\n */\n delay(\n wait: number,\n ...args: any[]\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes `func` with arguments reversed.\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to flip arguments for.\n * @returns {Function} Returns the new function.\n * @example\n *\n * var flipped = _.flip(function() {\n * return _.toArray(arguments);\n * });\n *\n * flipped('a', 'b', 'c', 'd');\n * // => ['d', 'c', 'b', 'a']\n */\n flip(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flip\n */\n flip(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flip\n */\n flip(): LoDashExplicitObjectWrapper;\n }\n\n //_.flow\n interface LoDashStatic {\n /**\n * Creates a function that returns the result of invoking the provided functions with the this binding of the\n * created function, where each successive invocation is supplied the return value of the previous.\n *\n * @param funcs Functions to invoke.\n * @return Returns the new function.\n */\n // 0-argument first function\n flow(f1: () => R1, f2: (a: R1) => R2): () => R2;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): () => R3;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): () => R4;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): () => R5;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): () => R6;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): () => R7;\n // 1-argument first function\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2): (a1: A1) => R2;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1) => R3;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1) => R4;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1) => R5;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1) => R6;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1) => R7;\n // 2-argument first function\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2) => R2;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2) => R3;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2) => R4;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2) => R5;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2) => R6;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2) => R7;\n // 3-argument first function\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2, a3: A3) => R2;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2, a3: A3) => R3;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2, a3: A3) => R4;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2, a3: A3) => R5;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2, a3: A3) => R6;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2, a3: A3) => R7;\n // 4-argument first function\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2, a3: A3, a4: A4) => R2;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2, a3: A3, a4: A4) => R3;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2, a3: A3, a4: A4) => R4;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2, a3: A3, a4: A4) => R5;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2, a3: A3, a4: A4) => R6;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2, a3: A3, a4: A4) => R7;\n // generic function\n flow(...funcs: Function[]): TResult;\n flow(funcs: Function[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flow\n */\n flow(...funcs: Function[]): LoDashImplicitObjectWrapper;\n /**\n * @see _.flow\n */\n flow(funcs: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flow\n */\n flow(...funcs: Function[]): LoDashExplicitObjectWrapper;\n /**\n * @see _.flow\n */\n flow(funcs: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.flowRight\n interface LoDashStatic {\n /**\n * This method is like _.flow except that it creates a function that invokes the provided functions from right\n * to left.\n *\n * @param funcs Functions to invoke.\n * @return Returns the new function.\n */\n flowRight(...funcs: Function[]): TResult;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flowRight\n */\n flowRight(...funcs: Function[]): LoDashImplicitObjectWrapper;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flowRight\n */\n flowRight(...funcs: Function[]): LoDashExplicitObjectWrapper;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.memoize\n interface MemoizedFunction extends Function {\n cache: MapCache;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that memoizes the result of func. If resolver is provided it determines the cache key for\n * storing the result based on the arguments provided to the memoized function. By default, the first argument\n * provided to the memoized function is coerced to a string and used as the cache key. The func is invoked with\n * the this binding of the memoized function.\n *\n * @param func The function to have its output memoized.\n * @param resolver The function to resolve the cache key.\n * @return Returns the new memoizing function.\n */\n memoize: {\n (func: T, resolver?: Function): T & MemoizedFunction;\n Cache: MapCacheConstructor;\n };\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.memoize\n */\n memoize(resolver?: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.memoize\n */\n memoize(resolver?: Function): LoDashExplicitObjectWrapper;\n }\n\n //_.overArgs (was _.modArgs)\n interface LoDashStatic {\n /**\n * Creates a function that runs each argument through a corresponding transform function.\n *\n * @param func The function to wrap.\n * @param transforms The functions to transform arguments, specified as individual functions or arrays\n * of functions.\n * @return Returns the new function.\n */\n overArgs(\n func: T,\n ...transforms: Function[]\n ): TResult;\n\n /**\n * @see _.overArgs\n */\n overArgs(\n func: T,\n transforms: Function[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overArgs\n */\n overArgs(...transforms: Function[]): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.overArgs\n */\n overArgs(transforms: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overArgs\n */\n overArgs(...transforms: Function[]): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.overArgs\n */\n overArgs(transforms: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.negate\n interface LoDashStatic {\n /**\n * Creates a function that negates the result of the predicate func. The func predicate is invoked with\n * the this binding and arguments of the created function.\n *\n * @param predicate The predicate to negate.\n * @return Returns the new function.\n */\n negate(predicate: T): (...args: any[]) => boolean;\n\n /**\n * @see _.negate\n */\n negate(predicate: T): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.negate\n */\n negate(): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n\n /**\n * @see _.negate\n */\n negate(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.negate\n */\n negate(): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n\n /**\n * @see _.negate\n */\n negate(): LoDashExplicitObjectWrapper;\n }\n\n //_.once\n interface LoDashStatic {\n /**\n * Creates a function that is restricted to invoking func once. Repeat calls to the function return the value\n * of the first call. The func is invoked with the this binding and arguments of the created function.\n *\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n once(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.once\n */\n once(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.once\n */\n once(): LoDashExplicitObjectWrapper;\n }\n\n //_.partial\n interface LoDashStatic {\n /**\n * Creates a function that, when called, invokes func with any additional partial arguments\n * prepended to those provided to the new function. This method is similar to _.bind except\n * it does not alter the this binding.\n * @param func The function to partially apply arguments to.\n * @param args Arguments to be partially applied.\n * @return The new partially applied function.\n **/\n partial: Partial;\n }\n\n type PH = LoDashStatic;\n\n type Function0 = () => R;\n type Function1 = (t1: T1) => R;\n type Function2 = (t1: T1, t2: T2) => R;\n type Function3 = (t1: T1, t2: T2, t3: T3) => R;\n type Function4 = (t1: T1, t2: T2, t3: T3, t4: T4) => R;\n\n interface Partial {\n // arity 0\n (func: Function0): Function0;\n // arity 1\n (func: Function1): Function1;\n (func: Function1, arg1: T1): Function0;\n // arity 2\n (func: Function2): Function2;\n (func: Function2, arg1: T1): Function1< T2, R>;\n (func: Function2, plc1: PH, arg2: T2): Function1;\n (func: Function2, arg1: T1, arg2: T2): Function0< R>;\n // arity 3\n (func: Function3): Function3;\n (func: Function3, arg1: T1): Function2< T2, T3, R>;\n (func: Function3, plc1: PH, arg2: T2): Function2;\n (func: Function3, arg1: T1, arg2: T2): Function1< T3, R>;\n (func: Function3, plc1: PH, plc2: PH, arg3: T3): Function2;\n (func: Function3, arg1: T1, plc2: PH, arg3: T3): Function1< T2, R>;\n (func: Function3, plc1: PH, arg2: T2, arg3: T3): Function1;\n (func: Function3, arg1: T1, arg2: T2, arg3: T3): Function0< R>;\n // arity 4\n (func: Function4): Function4;\n (func: Function4, arg1: T1): Function3< T2, T3, T4, R>;\n (func: Function4, plc1: PH, arg2: T2): Function3;\n (func: Function4, arg1: T1, arg2: T2): Function2< T3, T4, R>;\n (func: Function4, plc1: PH, plc2: PH, arg3: T3): Function3;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3): Function2< T2, T4, R>;\n (func: Function4, plc1: PH, arg2: T2, arg3: T3): Function2;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3): Function1< T4, R>;\n (func: Function4, plc1: PH, plc2: PH, plc3: PH, arg4: T4): Function3;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, arg4: T4): Function2< T2, T3, R>;\n (func: Function4, plc1: PH, arg2: T2, plc3: PH, arg4: T4): Function2;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, arg4: T4): Function1< T3, R>;\n (func: Function4, plc1: PH, plc2: PH, arg3: T3, arg4: T4): Function2;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, arg4: T4): Function1< T2, R>;\n (func: Function4, plc1: PH, arg2: T2, arg3: T3, arg4: T4): Function1;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, arg4: T4): Function0< R>;\n // catch-all\n (func: Function, ...args: any[]): Function;\n }\n\n //_.partialRight\n interface LoDashStatic {\n /**\n * This method is like _.partial except that partial arguments are appended to those provided\n * to the new function.\n * @param func The function to partially apply arguments to.\n * @param args Arguments to be partially applied.\n * @return The new partially applied function.\n **/\n partialRight: PartialRight;\n }\n\n interface PartialRight {\n // arity 0\n (func: Function0): Function0;\n // arity 1\n (func: Function1): Function1;\n (func: Function1, arg1: T1): Function0;\n // arity 2\n (func: Function2): Function2;\n (func: Function2, arg1: T1, plc2: PH): Function1< T2, R>;\n (func: Function2, arg2: T2): Function1;\n (func: Function2, arg1: T1, arg2: T2): Function0< R>;\n // arity 3\n (func: Function3): Function3;\n (func: Function3, arg1: T1, plc2: PH, plc3: PH): Function2< T2, T3, R>;\n (func: Function3, arg2: T2, plc3: PH): Function2;\n (func: Function3, arg1: T1, arg2: T2, plc3: PH): Function1< T3, R>;\n (func: Function3, arg3: T3): Function2;\n (func: Function3, arg1: T1, plc2: PH, arg3: T3): Function1< T2, R>;\n (func: Function3, arg2: T2, arg3: T3): Function1;\n (func: Function3, arg1: T1, arg2: T2, arg3: T3): Function0< R>;\n // arity 4\n (func: Function4): Function4;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, plc4: PH): Function3< T2, T3, T4, R>;\n (func: Function4, arg2: T2, plc3: PH, plc4: PH): Function3;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, plc4: PH): Function2< T3, T4, R>;\n (func: Function4, arg3: T3, plc4: PH): Function3;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, plc4: PH): Function2< T2, T4, R>;\n (func: Function4, arg2: T2, arg3: T3, plc4: PH): Function2;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, plc4: PH): Function1< T4, R>;\n (func: Function4, arg4: T4): Function3;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, arg4: T4): Function2< T2, T3, R>;\n (func: Function4, arg2: T2, plc3: PH, arg4: T4): Function2;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, arg4: T4): Function1< T3, R>;\n (func: Function4, arg3: T3, arg4: T4): Function2;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, arg4: T4): Function1< T2, R>;\n (func: Function4, arg2: T2, arg3: T3, arg4: T4): Function1;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, arg4: T4): Function0< R>;\n // catch-all\n (func: Function, ...args: any[]): Function;\n }\n\n //_.rearg\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with arguments arranged according to the specified indexes where the\n * argument value at the first index is provided as the first argument, the argument value at the second index\n * is provided as the second argument, and so on.\n * @param func The function to rearrange arguments for.\n * @param indexes The arranged argument indexes, specified as individual indexes or arrays of indexes.\n * @return Returns the new function.\n */\n rearg(func: Function, indexes: number[]): TResult;\n\n /**\n * @see _.rearg\n */\n rearg(func: Function, ...indexes: number[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.rearg\n */\n rearg(indexes: number[]): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.rearg\n */\n rearg(...indexes: number[]): LoDashImplicitObjectWrapper;\n }\n\n //_.rest\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of the created function and arguments from start\n * and beyond provided as an array.\n *\n * Note: This method is based on the rest parameter.\n *\n * @param func The function to apply a rest parameter to.\n * @param start The start position of the rest parameter.\n * @return Returns the new function.\n */\n rest(\n func: Function,\n start?: number\n ): TResult;\n\n /**\n * @see _.rest\n */\n rest(\n func: TFunc,\n start?: number\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.rest\n */\n rest(start?: number): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.rest\n */\n rest(start?: number): LoDashExplicitObjectWrapper;\n }\n\n //_.spread\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of the created function and an array of arguments\n * much like Function#apply.\n *\n * Note: This method is based on the spread operator.\n *\n * @param func The function to spread arguments over.\n * @return Returns the new function.\n */\n spread(func: F): T;\n\n /**\n * @see _.spread\n */\n spread(func: Function): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.spread\n */\n spread(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.spread\n */\n spread(): LoDashExplicitObjectWrapper;\n }\n\n //_.throttle\n interface ThrottleSettings {\n /**\n * If you'd like to disable the leading-edge call, pass this as false.\n */\n leading?: boolean;\n\n /**\n * If you'd like to disable the execution on the trailing-edge, pass false.\n */\n trailing?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Creates a throttled function that only invokes func at most once per every wait milliseconds. The throttled\n * function comes with a cancel method to cancel delayed invocations and a flush method to immediately invoke\n * them. Provide an options object to indicate that func should be invoked on the leading and/or trailing edge\n * of the wait timeout. Subsequent calls to the throttled function return the result of the last func call.\n *\n * Note: If leading and trailing options are true, func is invoked on the trailing edge of the timeout only if\n * the the throttled function is invoked more than once during the wait timeout.\n *\n * @param func The function to throttle.\n * @param wait The number of milliseconds to throttle invocations to.\n * @param options The options object.\n * @param options.leading Specify invoking on the leading edge of the timeout.\n * @param options.trailing Specify invoking on the trailing edge of the timeout.\n * @return Returns the new throttled function.\n */\n throttle(\n func: T,\n wait?: number,\n options?: ThrottleSettings\n ): T & Cancelable;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.throttle\n */\n throttle(\n wait?: number,\n options?: ThrottleSettings\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.throttle\n */\n throttle(\n wait?: number,\n options?: ThrottleSettings\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.unary\n interface LoDashStatic {\n /**\n * Creates a function that accepts up to one argument, ignoring any\n * additional arguments.\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to cap arguments for.\n * @returns {Function} Returns the new function.\n * @example\n *\n * _.map(['6', '8', '10'], _.unary(parseInt));\n * // => [6, 8, 10]\n */\n unary(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.unary\n */\n unary(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.unary\n */\n unary(): LoDashExplicitObjectWrapper;\n }\n\n //_.wrap\n interface LoDashStatic {\n /**\n * Creates a function that provides value to the wrapper function as its first argument. Any additional\n * arguments provided to the function are appended to those provided to the wrapper function. The wrapper is\n * invoked with the this binding of the created function.\n *\n * @param value The value to wrap.\n * @param wrapper The wrapper function.\n * @return Returns the new function.\n */\n wrap(\n value: V,\n wrapper: W\n ): R;\n\n /**\n * @see _.wrap\n */\n wrap(\n value: V,\n wrapper: Function\n ): R;\n\n /**\n * @see _.wrap\n */\n wrap(\n value: any,\n wrapper: Function\n ): R;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n /********\n * Lang *\n ********/\n\n //_.castArray\n interface LoDashStatic {\n /**\n * Casts value as an array if it’s not one.\n *\n * @param value The value to inspect.\n * @return Returns the cast array.\n */\n castArray(value?: Many): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.castArray\n */\n castArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.castArray\n */\n castArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): LoDashExplicitArrayWrapper;\n }\n\n //_.clone\n interface LoDashStatic {\n /**\n * Creates a shallow clone of value.\n *\n * Note: This method is loosely based on the structured clone algorithm and supports cloning arrays,\n * array buffers, booleans, date objects, maps, numbers, Object objects, regexes, sets, strings, symbols,\n * and typed arrays. The own enumerable properties of arguments objects are cloned as plain objects. An empty\n * object is returned for uncloneable values such as error objects, functions, DOM nodes, and WeakMaps.\n *\n * @param value The value to clone.\n * @return Returns the cloned value.\n */\n clone(value: T): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.clone\n */\n clone(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.clone\n */\n clone(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TWrapper;\n }\n\n //_.cloneDeep\n interface LoDashStatic {\n /**\n * This method is like _.clone except that it recursively clones value.\n *\n * @param value The value to recursively clone.\n * @return Returns the deep cloned value.\n */\n cloneDeep(value: T): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TWrapper;\n }\n\n //_.cloneDeepWith\n type CloneDeepWithCustomizer = (value: TValue, key?: number|string, object?: any, stack?: any) => TResult;\n\n interface LoDashStatic {\n /**\n * This method is like _.cloneWith except that it recursively clones value.\n *\n * @param value The value to recursively clone.\n * @param customizer The function to customize cloning.\n * @return Returns the deep cloned value.\n */\n cloneDeepWith(\n value: any,\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n\n /**\n * @see _.clonDeepeWith\n */\n cloneDeepWith(\n value: T,\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.cloneWith\n type CloneWithCustomizer = (value: TValue, key?: number|string, object?: any, stack?: any) => TResult;\n\n interface LoDashStatic {\n /**\n * This method is like _.clone except that it accepts customizer which is invoked to produce the cloned value.\n * If customizer returns undefined cloning is handled by the method instead.\n *\n * @param value The value to clone.\n * @param customizer The function to customize cloning.\n * @return Returns the cloned value.\n */\n cloneWith(\n value: any,\n customizer?: CloneWithCustomizer\n ): TResult;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n value: T,\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n /**\n * An object containing predicate functions for each property of T\n */\n type ConformsPredicateObject = {\n [P in keyof T]: (val: T[P]) => boolean;\n };\n\n //_.conforms\n interface LoDashStatic {\n /**\n * Creates a function that invokes the predicate properties of `source` with the corresponding\n * property values of a given object, returning true if all predicates return truthy, else false.\n */\n conforms(source: ConformsPredicateObject): (Target: T) => boolean;\n }\n\n //_.conformsTo\n interface LoDashStatic {\n /**\n * Checks if object conforms to source by invoking the predicate properties of source with the\n * corresponding property values of object.\n *\n * Note: This method is equivalent to _.conforms when source is partially applied.\n */\n conformsTo(object: T, source: ConformsPredicateObject): boolean;\n }\n\n //_.eq\n interface LoDashStatic {\n /**\n * Performs a [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'user': 'fred' };\n * var other = { 'user': 'fred' };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\n eq(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n eq(\n other: any\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n eq(\n other: any\n ): LoDashExplicitWrapper;\n }\n\n //_.gt\n interface LoDashStatic {\n /**\n * Checks if value is greater than other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is greater than other, else false.\n */\n gt(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.gt\n */\n gt(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.gt\n */\n gt(other: any): LoDashExplicitWrapper;\n }\n\n //_.gte\n interface LoDashStatic {\n /**\n * Checks if value is greater than or equal to other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is greater than or equal to other, else false.\n */\n gte(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.gte\n */\n gte(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.gte\n */\n gte(other: any): LoDashExplicitWrapper;\n }\n\n //_.isArguments\n interface LoDashStatic {\n /**\n * Checks if value is classified as an arguments object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isArguments(value?: any): value is IArguments;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArguments\n */\n isArguments(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArguments\n */\n isArguments(): LoDashExplicitWrapper;\n }\n\n //_.isArray\n interface LoDashStatic {\n /**\n * Checks if value is classified as an Array object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isArray(value?: any): value is any[];\n\n /**\n * DEPRECATED\n */\n isArray(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArray\n */\n isArray(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArray\n */\n isArray(): LoDashExplicitWrapper;\n }\n\n //_.isArrayBuffer\n interface LoDashStatic {\n /**\n * Checks if value is classified as an ArrayBuffer object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isArrayBuffer(value?: any): value is ArrayBuffer;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayBuffer\n */\n isArrayBuffer(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayBuffer\n */\n isArrayBuffer(): LoDashExplicitWrapper;\n }\n\n //_.isArrayLike\n interface LoDashStatic {\n /**\n * Checks if `value` is array-like. A value is considered array-like if it's\n * not a function and has a `value.length` that's an integer greater than or\n * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.\n *\n * @static\n * @memberOf _\n * @type Function\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n * @example\n *\n * _.isArrayLike([1, 2, 3]);\n * // => true\n *\n * _.isArrayLike(document.body.children);\n * // => true\n *\n * _.isArrayLike('abc');\n * // => true\n *\n * _.isArrayLike(_.noop);\n * // => false\n */\n isArrayLike(value: T & string & number): boolean; // should only match if T = any\n\n /**\n * @see _.isArrayLike\n */\n isArrayLike(value?: Function): value is never;\n\n /**\n * @see _.isArrayLike\n */\n isArrayLike(value: T | Function): value is T & { length: number };\n\n /**\n * DEPRECATED\n */\n isArrayLike(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayLike\n */\n isArrayLike(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayLike\n */\n isArrayLike(): LoDashExplicitWrapper;\n }\n\n //_.isArrayLikeObject\n interface LoDashStatic {\n /**\n * This method is like `_.isArrayLike` except that it also checks if `value`\n * is an object.\n *\n * @static\n * @memberOf _\n * @type Function\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.\n * @example\n *\n * _.isArrayLikeObject([1, 2, 3]);\n * // => true\n *\n * _.isArrayLikeObject(document.body.children);\n * // => true\n *\n * _.isArrayLikeObject('abc');\n * // => false\n *\n * _.isArrayLikeObject(_.noop);\n * // => false\n */\n isArrayLikeObject(value: T & string & number): boolean; // should only match if T = any\n\n /**\n * @see _.isArrayLike\n */\n isArrayLikeObject(value?: Function | string | boolean | number): value is never;\n\n /**\n * @see _.isArrayLike\n */\n isArrayLikeObject(value: T | Function | string | boolean | number): value is T & { length: number };\n\n /**\n * DEPRECATED\n */\n isArrayLikeObject(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayLikeObject\n */\n isArrayLikeObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayLikeObject\n */\n isArrayLikeObject(): LoDashExplicitWrapper;\n }\n\n //_.isBoolean\n interface LoDashStatic {\n /**\n * Checks if value is classified as a boolean primitive or object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isBoolean(value?: any): value is boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isBoolean\n */\n isBoolean(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isBoolean\n */\n isBoolean(): LoDashExplicitWrapper;\n }\n\n //_.isBuffer\n interface LoDashStatic {\n /**\n * Checks if value is a buffer.\n *\n * @param value The value to check.\n * @return Returns true if value is a buffer, else false.\n */\n isBuffer(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isBuffer\n */\n isBuffer(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isBuffer\n */\n isBuffer(): LoDashExplicitWrapper;\n }\n\n //_.isDate\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Date object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isDate(value?: any): value is Date;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isDate\n */\n isDate(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isDate\n */\n isDate(): LoDashExplicitWrapper;\n }\n\n //_.isElement\n interface LoDashStatic {\n /**\n * Checks if value is a DOM element.\n *\n * @param value The value to check.\n * @return Returns true if value is a DOM element, else false.\n */\n isElement(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isElement\n */\n isElement(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isElement\n */\n isElement(): LoDashExplicitWrapper;\n }\n\n //_.isEmpty\n interface LoDashStatic {\n /**\n * Checks if value is empty. A value is considered empty unless it’s an arguments object, array, string, or\n * jQuery-like collection with a length greater than 0 or an object with own enumerable properties.\n *\n * @param value The value to inspect.\n * @return Returns true if value is empty, else false.\n */\n isEmpty(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEmpty\n */\n isEmpty(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEmpty\n */\n isEmpty(): LoDashExplicitWrapper;\n }\n\n //_.isEqual\n interface LoDashStatic {\n /**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n *\n * **Note:** This method supports comparing arrays, array buffers, booleans,\n * date objects, error objects, maps, numbers, `Object` objects, regexes,\n * sets, strings, symbols, and typed arrays. `Object` objects are compared\n * by their own, not inherited, enumerable properties. Functions and DOM\n * nodes are **not** supported.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'user': 'fred' };\n * var other = { 'user': 'fred' };\n *\n * _.isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n */\n isEqual(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n isEqual(\n other: any\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n isEqual(\n other: any\n ): LoDashExplicitWrapper;\n }\n\n // _.isEqualWith\n type IsEqualCustomizer = (value: any, other: any, indexOrKey: number|string|undefined, parent: any, otherParent: any, stack: any) => boolean|undefined;\n\n interface LoDashStatic {\n /**\n * This method is like `_.isEqual` except that it accepts `customizer` which is\n * invoked to compare values. If `customizer` returns `undefined` comparisons are\n * handled by the method instead. The `customizer` is invoked with up to seven arguments:\n * (objValue, othValue [, index|key, object, other, stack]).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @param {Function} [customizer] The function to customize comparisons.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * function isGreeting(value) {\n * return /^h(?:i|ello)$/.test(value);\n * }\n *\n * function customizer(objValue, othValue) {\n * if (isGreeting(objValue) && isGreeting(othValue)) {\n * return true;\n * }\n * }\n *\n * var array = ['hello', 'goodbye'];\n * var other = ['hi', 'goodbye'];\n *\n * _.isEqualWith(array, other, customizer);\n * // => true\n */\n isEqualWith(\n value: any,\n other: any,\n customizer?: IsEqualCustomizer\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqualWith\n */\n isEqualWith(\n other: any,\n customizer?: IsEqualCustomizer\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqualWith\n */\n isEqualWith(\n other: any,\n customizer?: IsEqualCustomizer\n ): LoDashExplicitWrapper;\n }\n\n //_.isError\n interface LoDashStatic {\n /**\n * Checks if value is an Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, or URIError\n * object.\n *\n * @param value The value to check.\n * @return Returns true if value is an error object, else false.\n */\n isError(value: any): value is Error;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isError\n */\n isError(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isError\n */\n isError(): LoDashExplicitWrapper;\n }\n\n //_.isFinite\n interface LoDashStatic {\n /**\n * Checks if value is a finite primitive number.\n *\n * Note: This method is based on Number.isFinite.\n *\n * @param value The value to check.\n * @return Returns true if value is a finite number, else false.\n */\n isFinite(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isFinite\n */\n isFinite(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isFinite\n */\n isFinite(): LoDashExplicitWrapper;\n }\n\n //_.isFunction\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Function object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isFunction(value?: any): value is Function;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isFunction\n */\n isFunction(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isFunction\n */\n isFunction(): LoDashExplicitWrapper;\n }\n\n //_.isInteger\n interface LoDashStatic {\n /**\n * Checks if `value` is an integer.\n *\n * **Note:** This method is based on [`Number.isInteger`](https://mdn.io/Number/isInteger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an integer, else `false`.\n * @example\n *\n * _.isInteger(3);\n * // => true\n *\n * _.isInteger(Number.MIN_VALUE);\n * // => false\n *\n * _.isInteger(Infinity);\n * // => false\n *\n * _.isInteger('3');\n * // => false\n */\n isInteger(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isInteger\n */\n isInteger(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isInteger\n */\n isInteger(): LoDashExplicitWrapper;\n }\n\n //_.isLength\n interface LoDashStatic {\n /**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n * @example\n *\n * _.isLength(3);\n * // => true\n *\n * _.isLength(Number.MIN_VALUE);\n * // => false\n *\n * _.isLength(Infinity);\n * // => false\n *\n * _.isLength('3');\n * // => false\n */\n isLength(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isLength\n */\n isLength(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isLength\n */\n isLength(): LoDashExplicitWrapper;\n }\n\n //_.isMap\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Map object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isMap(value?: any): value is Map;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isMap\n */\n isMap(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isMap\n */\n isMap(): LoDashExplicitWrapper;\n }\n\n //_.isMatch\n type isMatchCustomizer = (value: any, other: any, indexOrKey?: number|string) => boolean;\n\n interface LoDashStatic {\n /**\n * Performs a deep comparison between `object` and `source` to determine if\n * `object` contains equivalent property values.\n *\n * **Note:** This method supports comparing the same values as `_.isEqual`.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {Object} object The object to inspect.\n * @param {Object} source The object of property values to match.\n * @returns {boolean} Returns `true` if `object` is a match, else `false`.\n * @example\n *\n * var object = { 'user': 'fred', 'age': 40 };\n *\n * _.isMatch(object, { 'age': 40 });\n * // => true\n *\n * _.isMatch(object, { 'age': 36 });\n * // => false\n */\n isMatch(object: Object, source: Object): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.isMatch\n */\n isMatch(source: Object): boolean;\n }\n\n //_.isMatchWith\n type isMatchWithCustomizer = (value: any, other: any, indexOrKey?: number|string) => boolean;\n\n interface LoDashStatic {\n /**\n * This method is like `_.isMatch` except that it accepts `customizer` which\n * is invoked to compare values. If `customizer` returns `undefined` comparisons\n * are handled by the method instead. The `customizer` is invoked with three\n * arguments: (objValue, srcValue, index|key, object, source).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {Object} object The object to inspect.\n * @param {Object} source The object of property values to match.\n * @param {Function} [customizer] The function to customize comparisons.\n * @returns {boolean} Returns `true` if `object` is a match, else `false`.\n * @example\n *\n * function isGreeting(value) {\n * return /^h(?:i|ello)$/.test(value);\n * }\n *\n * function customizer(objValue, srcValue) {\n * if (isGreeting(objValue) && isGreeting(srcValue)) {\n * return true;\n * }\n * }\n *\n * var object = { 'greeting': 'hello' };\n * var source = { 'greeting': 'hi' };\n *\n * _.isMatchWith(object, source, customizer);\n * // => true\n */\n isMatchWith(object: Object, source: Object, customizer: isMatchWithCustomizer): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.isMatchWith\n */\n isMatchWith(source: Object, customizer: isMatchWithCustomizer): boolean;\n }\n\n //_.isNaN\n interface LoDashStatic {\n /**\n * Checks if value is NaN.\n *\n * Note: This method is not the same as isNaN which returns true for undefined and other non-numeric values.\n *\n * @param value The value to check.\n * @return Returns true if value is NaN, else false.\n */\n isNaN(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isNaN\n */\n isNaN(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isNaN\n */\n isNaN(): LoDashExplicitWrapper;\n }\n\n //_.isNative\n interface LoDashStatic {\n /**\n * Checks if value is a native function.\n * @param value The value to check.\n *\n * @retrun Returns true if value is a native function, else false.\n */\n isNative(value: any): value is Function;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNative\n */\n isNative(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNative\n */\n isNative(): LoDashExplicitWrapper;\n }\n\n //_.isNil\n interface LoDashStatic {\n /**\n * Checks if `value` is `null` or `undefined`.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is nullish, else `false`.\n * @example\n *\n * _.isNil(null);\n * // => true\n *\n * _.isNil(void 0);\n * // => true\n *\n * _.isNil(NaN);\n * // => false\n */\n isNil(value: any): value is null | undefined;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNil\n */\n isNil(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNil\n */\n isNil(): LoDashExplicitWrapper;\n }\n\n //_.isNull\n interface LoDashStatic {\n /**\n * Checks if value is null.\n *\n * @param value The value to check.\n * @return Returns true if value is null, else false.\n */\n isNull(value: any): value is null;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNull\n */\n isNull(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNull\n */\n isNull(): LoDashExplicitWrapper;\n }\n\n //_.isNumber\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Number primitive or object.\n *\n * Note: To exclude Infinity, -Infinity, and NaN, which are classified as numbers, use the _.isFinite method.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isNumber(value?: any): value is number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNumber\n */\n isNumber(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNumber\n */\n isNumber(): LoDashExplicitWrapper;\n }\n\n //_.isObject\n interface LoDashStatic {\n /**\n * Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0),\n * and new String(''))\n *\n * @param value The value to check.\n * @return Returns true if value is an object, else false.\n */\n isObject(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isObject\n */\n isObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isObject\n */\n isObject(): LoDashExplicitWrapper;\n }\n\n //_.isObjectLike\n interface LoDashStatic {\n /**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\n isObjectLike(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isObjectLike\n */\n isObjectLike(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isObjectLike\n */\n isObjectLike(): LoDashExplicitWrapper;\n }\n\n //_.isPlainObject\n interface LoDashStatic {\n /**\n * Checks if value is a plain object, that is, an object created by the Object constructor or one with a\n * [[Prototype]] of null.\n *\n * Note: This method assumes objects created by the Object constructor have no inherited enumerable properties.\n *\n * @param value The value to check.\n * @return Returns true if value is a plain object, else false.\n */\n isPlainObject(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isPlainObject\n */\n isPlainObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isPlainObject\n */\n isPlainObject(): LoDashExplicitWrapper;\n }\n\n //_.isRegExp\n interface LoDashStatic {\n /**\n * Checks if value is classified as a RegExp object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isRegExp(value?: any): value is RegExp;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isRegExp\n */\n isRegExp(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isRegExp\n */\n isRegExp(): LoDashExplicitWrapper;\n }\n\n //_.isSafeInteger\n interface LoDashStatic {\n /**\n * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754\n * double precision number which isn't the result of a rounded unsafe integer.\n *\n * **Note:** This method is based on [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.\n * @example\n *\n * _.isSafeInteger(3);\n * // => true\n *\n * _.isSafeInteger(Number.MIN_VALUE);\n * // => false\n *\n * _.isSafeInteger(Infinity);\n * // => false\n *\n * _.isSafeInteger('3');\n * // => false\n */\n isSafeInteger(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isSafeInteger\n */\n isSafeInteger(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isSafeInteger\n */\n isSafeInteger(): LoDashExplicitWrapper;\n }\n\n //_.isSet\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Set object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isSet(value?: any): value is Set;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isSet(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isSet(): LoDashExplicitWrapper;\n }\n\n //_.isString\n interface LoDashStatic {\n /**\n * Checks if value is classified as a String primitive or object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isString(value?: any): value is string;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isString\n */\n isString(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isString\n */\n isString(): LoDashExplicitWrapper;\n }\n\n //_.isSymbol\n interface LoDashStatic {\n /**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\n isSymbol(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isSymbol\n */\n isSymbol(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isSymbol\n */\n isSymbol(): LoDashExplicitWrapper;\n }\n\n //_.isTypedArray\n interface LoDashStatic {\n /**\n * Checks if value is classified as a typed array.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isTypedArray(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isTypedArray\n */\n isTypedArray(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isTypedArray\n */\n isTypedArray(): LoDashExplicitWrapper;\n }\n\n //_.isUndefined\n interface LoDashStatic {\n /**\n * Checks if value is undefined.\n *\n * @param value The value to check.\n * @return Returns true if value is undefined, else false.\n */\n isUndefined(value: any): value is undefined;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isUndefined\n */\n isUndefined(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isUndefined\n */\n isUndefined(): LoDashExplicitWrapper;\n }\n\n //_.isWeakMap\n interface LoDashStatic {\n /**\n * Checks if value is classified as a WeakMap object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isWeakMap(value?: any): value is WeakMap;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isWeakMap(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isWeakMap(): LoDashExplicitWrapper;\n }\n\n //_.isWeakSet\n interface LoDashStatic {\n /**\n * Checks if value is classified as a WeakSet object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isWeakSet(value?: any): value is WeakSet;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isWeakSet\n */\n isWeakSet(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isWeakSet\n */\n isWeakSet(): LoDashExplicitWrapper;\n }\n\n //_.lt\n interface LoDashStatic {\n /**\n * Checks if value is less than other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is less than other, else false.\n */\n lt(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.lt\n */\n lt(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.lt\n */\n lt(other: any): LoDashExplicitWrapper;\n }\n\n //_.lte\n interface LoDashStatic {\n /**\n * Checks if value is less than or equal to other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is less than or equal to other, else false.\n */\n lte(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.lte\n */\n lte(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.lte\n */\n lte(other: any): LoDashExplicitWrapper;\n }\n\n //_.toArray\n interface LoDashStatic {\n /**\n * Converts value to an array.\n *\n * @param value The value to convert.\n * @return Returns the converted array.\n */\n toArray(value: List|Dictionary|NumericDictionary): T[];\n\n /**\n * @see _.toArray\n */\n toArray(value: TValue): TResult[];\n\n /**\n * @see _.toArray\n */\n toArray(value?: any): TResult[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n //_.toPlainObject\n interface LoDashStatic {\n /**\n * Converts value to a plain object flattening inherited enumerable properties of value to own properties\n * of the plain object.\n *\n * @param value The value to convert.\n * @return Returns the converted plain object.\n */\n toPlainObject(value?: any): TResult;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toPlainObject\n */\n toPlainObject(): LoDashImplicitObjectWrapper;\n }\n\n //_.toFinite\n interface LoDashStatic {\n /**\n * Converts `value` to a finite number.\n *\n * @static\n * @memberOf _\n * @since 4.12.0\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted number.\n * @example\n *\n * _.toFinite(3.2);\n * // => 3.2\n *\n * _.toFinite(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toFinite(Infinity);\n * // => 1.7976931348623157e+308\n *\n * _.toFinite('3.2');\n * // => 3.2\n */\n toFinite(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toFinite\n */\n toFinite(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toFinite\n */\n toFinite(): LoDashExplicitWrapper;\n }\n\n //_.toInteger\n interface LoDashStatic {\n /**\n * Converts `value` to an integer.\n *\n * **Note:** This function is loosely based on [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted integer.\n * @example\n *\n * _.toInteger(3);\n * // => 3\n *\n * _.toInteger(Number.MIN_VALUE);\n * // => 0\n *\n * _.toInteger(Infinity);\n * // => 1.7976931348623157e+308\n *\n * _.toInteger('3');\n * // => 3\n */\n toInteger(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toInteger\n */\n toInteger(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toInteger\n */\n toInteger(): LoDashExplicitWrapper;\n }\n\n //_.toLength\n interface LoDashStatic {\n /**\n * Converts `value` to an integer suitable for use as the length of an\n * array-like object.\n *\n * **Note:** This method is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @return {number} Returns the converted integer.\n * @example\n *\n * _.toLength(3);\n * // => 3\n *\n * _.toLength(Number.MIN_VALUE);\n * // => 0\n *\n * _.toLength(Infinity);\n * // => 4294967295\n *\n * _.toLength('3');\n * // => 3\n */\n toLength(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toLength\n */\n toLength(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toLength\n */\n toLength(): LoDashExplicitWrapper;\n }\n\n //_.toNumber\n interface LoDashStatic {\n /**\n * Converts `value` to a number.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to process.\n * @returns {number} Returns the number.\n * @example\n *\n * _.toNumber(3);\n * // => 3\n *\n * _.toNumber(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toNumber(Infinity);\n * // => Infinity\n *\n * _.toNumber('3');\n * // => 3\n */\n toNumber(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toNumber\n */\n toNumber(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toNumber\n */\n toNumber(): LoDashExplicitWrapper;\n }\n\n //_.toSafeInteger\n interface LoDashStatic {\n /**\n * Converts `value` to a safe integer. A safe integer can be compared and\n * represented correctly.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted integer.\n * @example\n *\n * _.toSafeInteger(3);\n * // => 3\n *\n * _.toSafeInteger(Number.MIN_VALUE);\n * // => 0\n *\n * _.toSafeInteger(Infinity);\n * // => 9007199254740991\n *\n * _.toSafeInteger('3');\n * // => 3\n */\n toSafeInteger(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toSafeInteger\n */\n toSafeInteger(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toSafeInteger\n */\n toSafeInteger(): LoDashExplicitWrapper;\n }\n\n //_.toString DUMMY\n interface LoDashStatic {\n /**\n * Converts `value` to a string if it's not one. An empty string is returned\n * for `null` and `undefined` values. The sign of `-0` is preserved.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to process.\n * @returns {string} Returns the string.\n * @example\n *\n * _.toString(null);\n * // => ''\n *\n * _.toString(-0);\n * // => '-0'\n *\n * _.toString([1, 2, 3]);\n * // => '1,2,3'\n */\n toString(value: any): string;\n }\n\n /********\n * Math *\n ********/\n\n //_.add\n interface LoDashStatic {\n /**\n * Adds two numbers.\n *\n * @param augend The first number to add.\n * @param addend The second number to add.\n * @return Returns the sum.\n */\n add(\n augend: number,\n addend: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.add\n */\n add(addend: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.add\n */\n add(addend: number): LoDashExplicitWrapper;\n }\n\n //_.ceil\n interface LoDashStatic {\n /**\n * Calculates n rounded up to precision.\n *\n * @param n The number to round up.\n * @param precision The precision to round up to.\n * @return Returns the rounded up number.\n */\n ceil(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.ceil\n */\n ceil(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.ceil\n */\n ceil(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.divide\n interface LoDashStatic {\n /**\n * Divide two numbers.\n *\n * @param dividend The first number in a division.\n * @param divisor The second number in a division.\n * @returns Returns the quotient.\n */\n divide(\n dividend: number,\n divisor: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.divide\n */\n divide(divisor: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.divide\n */\n divide(divisor: number): LoDashExplicitWrapper;\n }\n\n //_.floor\n interface LoDashStatic {\n /**\n * Calculates n rounded down to precision.\n *\n * @param n The number to round down.\n * @param precision The precision to round down to.\n * @return Returns the rounded down number.\n */\n floor(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.floor\n */\n floor(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.floor\n */\n floor(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.max\n interface LoDashStatic {\n /**\n * Computes the maximum value of `array`. If `array` is empty or falsey\n * `undefined` is returned.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {*} Returns the maximum value.\n */\n max(\n collection: List | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.max\n */\n max(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.max\n */\n max(): T | undefined;\n }\n\n //_.maxBy\n interface LoDashStatic {\n /**\n * This method is like `_.max` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * the value is ranked. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {*} Returns the maximum value.\n * @example\n *\n * var objects = [{ 'n': 1 }, { 'n': 2 }];\n *\n * _.maxBy(objects, function(o) { return o.a; });\n * // => { 'n': 2 }\n *\n * // using the `_.property` iteratee shorthand\n * _.maxBy(objects, 'n');\n * // => { 'n': 2 }\n */\n maxBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: List|Dictionary | null | undefined,\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: ListIterator|DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n //_.mean\n interface LoDashStatic {\n /**\n * Computes the mean of the values in `array`.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {number} Returns the mean.\n * @example\n *\n * _.mean([4, 2, 8, 6]);\n * // => 5\n */\n mean(\n collection: List | null | undefined\n ): number;\n }\n\n //_.meanBy\n interface LoDashStatic {\n /**\n * Computes the mean of the provided propties of the objects in the `array`\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the mean.\n * @example\n *\n * _.mean([{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }], 'n');\n * // => 5\n */\n meanBy(\n collection: List | null | undefined,\n iteratee?: ListIterator | string\n ): number;\n\n meanBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator | string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.mean\n */\n mean(): number;\n\n /**\n * @see _.mean\n */\n mean(): number;\n }\n\n //_.min\n interface LoDashStatic {\n /**\n * Computes the minimum value of `array`. If `array` is empty or falsey\n * `undefined` is returned.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {*} Returns the minimum value.\n */\n min(\n collection: List | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.min\n */\n min(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.min\n */\n min(): T | undefined;\n }\n\n //_.minBy\n interface LoDashStatic {\n /**\n * This method is like `_.min` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * the value is ranked. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {*} Returns the minimum value.\n * @example\n *\n * var objects = [{ 'n': 1 }, { 'n': 2 }];\n *\n * _.minBy(objects, function(o) { return o.a; });\n * // => { 'n': 1 }\n *\n * // using the `_.property` iteratee shorthand\n * _.minBy(objects, 'n');\n * // => { 'n': 1 }\n */\n minBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: List|Dictionary | null | undefined,\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: ListIterator|DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n //_.multiply\n interface LoDashStatic {\n /**\n * Multiply two numbers.\n * @param multiplier The first number in a multiplication.\n * @param multiplicand The second number in a multiplication.\n * @returns Returns the product.\n */\n multiply(\n multiplier: number,\n multiplicand: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.multiply\n */\n multiply(multiplicand: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.multiply\n */\n multiply(multiplicand: number): LoDashExplicitWrapper;\n }\n\n //_.round\n interface LoDashStatic {\n /**\n * Calculates n rounded to precision.\n *\n * @param n The number to round.\n * @param precision The precision to round to.\n * @return Returns the rounded number.\n */\n round(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.round\n */\n round(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.round\n */\n round(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.sum\n interface LoDashStatic {\n /**\n * Computes the sum of the values in `array`.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {number} Returns the sum.\n * @example\n *\n * _.sum([4, 2, 8, 6]);\n * // => 20\n */\n sum(collection: List | null | undefined): number;\n\n /**\n * @see _.sum\n */\n sum(collection: List|Dictionary | null | undefined): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sum\n */\n sum(): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sum\n **/\n sum(): number;\n\n /**\n * @see _.sum\n */\n sum(): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n }\n\n //_.sumBy\n interface LoDashStatic {\n /**\n * This method is like `_.sum` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the value to be summed.\n * The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the sum.\n * @example\n *\n * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];\n *\n * _.sumBy(objects, function(o) { return o.n; });\n * // => 20\n *\n * // using the `_.property` iteratee shorthand\n * _.sumBy(objects, 'n');\n * // => 20\n */\n sumBy(\n collection: List | null | undefined,\n iteratee: ListIterator\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List<{}> | null | undefined,\n iteratee: string\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List | null | undefined\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List<{}> | null | undefined,\n iteratee: Dictionary<{}>\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator<{}, number>\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator<{}, number>\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): LoDashExplicitWrapper;\n }\n\n /**********\n * Number *\n **********/\n\n //_.subtract\n interface LoDashStatic {\n /**\n * Subtract two numbers.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {number} minuend The first number in a subtraction.\n * @param {number} subtrahend The second number in a subtraction.\n * @returns {number} Returns the difference.\n * @example\n *\n * _.subtract(6, 4);\n * // => 2\n */\n subtract(\n minuend: number,\n subtrahend: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.subtract\n */\n subtract(\n subtrahend: number\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.subtract\n */\n subtract(\n subtrahend: number\n ): LoDashExplicitWrapper;\n }\n\n //_.clamp\n interface LoDashStatic {\n /**\n * Clamps `number` within the inclusive `lower` and `upper` bounds.\n *\n * @static\n * @memberOf _\n * @category Number\n * @param {number} number The number to clamp.\n * @param {number} [lower] The lower bound.\n * @param {number} upper The upper bound.\n * @returns {number} Returns the clamped number.\n * @example\n *\n * _.clamp(-10, -5, 5);\n * // => -5\n *\n * _.clamp(10, -5, 5);\n * // => 5\n */\n clamp(\n number: number,\n lower: number,\n upper: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.clamp\n */\n clamp(\n lower: number,\n upper: number\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.clamp\n */\n clamp(\n lower: number,\n upper: number\n ): LoDashExplicitWrapper;\n }\n\n //_.inRange\n interface LoDashStatic {\n /**\n * Checks if n is between start and up to but not including, end. If end is not specified it’s set to start\n * with start then set to 0.\n *\n * @param n The number to check.\n * @param start The start of the range.\n * @param end The end of the range.\n * @return Returns true if n is in the range, else false.\n */\n inRange(\n n: number,\n start: number,\n end: number\n ): boolean;\n\n /**\n * @see _.inRange\n */\n inRange(\n n: number,\n end: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.inRange\n */\n inRange(\n start: number,\n end: number\n ): boolean;\n\n /**\n * @see _.inRange\n */\n inRange(end: number): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.inRange\n */\n inRange(\n start: number,\n end: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.inRange\n */\n inRange(end: number): LoDashExplicitWrapper;\n }\n\n //_.random\n interface LoDashStatic {\n /**\n * Produces a random number between min and max (inclusive). If only one argument is provided a number between\n * 0 and the given number is returned. If floating is true, or either min or max are floats, a floating-point\n * number is returned instead of an integer.\n *\n * @param min The minimum possible value.\n * @param max The maximum possible value.\n * @param floating Specify returning a floating-point number.\n * @return Returns the random number.\n */\n random(\n min?: number,\n max?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(\n min?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.random\n */\n random(\n max?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.random\n */\n random(\n max?: number,\n floating?: boolean\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): LoDashExplicitWrapper;\n }\n\n /**********\n * Object *\n **********/\n\n //_.assign\n interface LoDashStatic {\n /**\n * Assigns own enumerable properties of source objects to the destination\n * object. Source objects are applied from left to right. Subsequent sources\n * overwrite property assignments of previous sources.\n *\n * **Note:** This method mutates `object` and is loosely based on\n * [`Object.assign`](https://mdn.io/Object/assign).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function Foo() {\n * this.c = 3;\n * }\n *\n * function Bar() {\n * this.e = 5;\n * }\n *\n * Foo.prototype.d = 4;\n * Bar.prototype.f = 6;\n *\n * _.assign({ 'a': 1 }, new Foo, new Bar);\n * // => { 'a': 1, 'c': 3, 'e': 5 }\n */\n assign(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assign\n */\n assign(object: TObject): TObject;\n\n /**\n * @see _.assign\n */\n assign(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assign\n */\n assign(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assign\n */\n assign(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method is like `_.assign` except that it accepts `customizer` which\n * is invoked to produce the assigned values. If `customizer` returns `undefined`\n * assignment is handled by the method instead. The `customizer` is invoked\n * with five arguments: (objValue, srcValue, key, object, source).\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} [customizer] The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * return _.isUndefined(objValue) ? srcValue : objValue;\n * }\n *\n * var defaults = _.partialRight(_.assignWith, customizer);\n *\n * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });\n * // => { 'a': 1, 'b': 2 }\n */\n assignWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignWith\n */\n assignWith(object: TObject): TObject;\n\n /**\n * @see _.assignWith\n */\n assignWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignWith\n */\n assignWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignWith\n */\n assignWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.assignIn\n interface LoDashStatic {\n /**\n * This method is like `_.assign` except that it iterates over own and\n * inherited source properties.\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @alias extend\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function Foo() {\n * this.b = 2;\n * }\n *\n * function Bar() {\n * this.d = 4;\n * }\n *\n * Foo.prototype.c = 3;\n * Bar.prototype.e = 5;\n *\n * _.assignIn({ 'a': 1 }, new Foo, new Bar);\n * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }\n */\n assignIn(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignIn\n */\n assignIn(object: TObject): TObject;\n\n /**\n * @see _.assignIn\n */\n assignIn(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n assignIn(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n assignIn(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.assignInWith\n type AssignCustomizer = (objectValue: any, sourceValue: any, key?: string, object?: {}, source?: {}) => any;\n\n interface LoDashStatic {\n /**\n * This method is like `_.assignIn` except that it accepts `customizer` which\n * is invoked to produce the assigned values. If `customizer` returns `undefined`\n * assignment is handled by the method instead. The `customizer` is invoked\n * with five arguments: (objValue, srcValue, key, object, source).\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @alias extendWith\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} [customizer] The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * return _.isUndefined(objValue) ? srcValue : objValue;\n * }\n *\n * var defaults = _.partialRight(_.assignInWith, customizer);\n *\n * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });\n * // => { 'a': 1, 'b': 2 }\n */\n assignInWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(object: TObject): TObject;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n assignInWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n assignInWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.create\n interface LoDashStatic {\n /**\n * Creates an object that inherits from the given prototype object. If a properties object is provided its own\n * enumerable properties are assigned to the created object.\n *\n * @param prototype The object to inherit from.\n * @param properties The properties to assign to the object.\n * @return Returns the new object.\n */\n create(\n prototype: T,\n properties?: U\n ): T & U;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.create\n */\n create(properties?: U): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.create\n */\n create(properties?: U): LoDashExplicitObjectWrapper;\n }\n\n //_.defaults\n interface LoDashStatic {\n /**\n * Assigns own enumerable properties of source object(s) to the destination object for all destination\n * properties that resolve to undefined. Once a property is set, additional values of the same property are\n * ignored.\n *\n * Note: This method mutates object.\n *\n * @param object The destination object.\n * @param sources The source objects.\n * @return The destination object.\n */\n defaults(\n object: TObject,\n source: TSource\n ): TSource & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TSource3 & TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TSource4 & TSource3 & TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(object: TObject): TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: any,\n ...sources: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defaults\n */\n defaults(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(...sources: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.defaults\n */\n defaults(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(...sources: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.defaultsDeep\n interface LoDashStatic {\n /**\n * This method is like _.defaults except that it recursively assigns default properties.\n * @param object The destination object.\n * @param sources The source objects.\n * @return Returns object.\n **/\n defaultsDeep(\n object: T,\n ...sources: any[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defaultsDeep\n **/\n defaultsDeep(...sources: any[]): LoDashImplicitObjectWrapper;\n }\n\n // _.extend\n interface LoDashStatic {\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignIn\n */\n extend(object: TObject): TObject;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n extend(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n extend(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignInWith\n */\n extendWith(object: TObject): TObject;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n extendWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n extendWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.findKey\n interface LoDashStatic {\n /**\n * This method is like _.find except that it returns the key of the first element predicate returns truthy for\n * instead of the element itself.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param object The object to search.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the key of the matched element, else undefined.\n */\n findKey(\n object: TObject,\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n object: TObject,\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n object: TObject,\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey, TObject>(\n object: TObject,\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey>(\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: DictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey>(\n predicate?: TWhere\n ): LoDashExplicitWrapper;\n }\n\n //_.findLastKey\n interface LoDashStatic {\n /**\n * This method is like _.findKey except that it iterates over elements of a collection in the opposite order.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param object The object to search.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the key of the matched element, else undefined.\n */\n findLastKey(\n object: TObject,\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n object: TObject,\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n object: TObject,\n predicate?: string\n ): string;\n\n /**\n * @see _.findLastKey\n */\n findLastKey, TObject>(\n object: TObject,\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: DictionaryIterator\n ): string;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey>(\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: DictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey>(\n predicate?: TWhere\n ): LoDashExplicitWrapper;\n }\n\n //_.forIn\n interface LoDashStatic {\n /**\n * Iterates over own and inherited enumerable properties of an object invoking iteratee for each property. The\n * iteratee is bound to thisArg and invoked with three arguments: (value, key, object). Iteratee functions may\n * exit iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forIn(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forIn\n */\n forIn(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forIn\n */\n forIn(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forIn\n */\n forIn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forIn\n */\n forIn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forInRight\n interface LoDashStatic {\n /**\n * This method is like _.forIn except that it iterates over properties of object in the opposite order.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forInRight(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forInRight\n */\n forInRight(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forInRight\n */\n forInRight(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forInRight\n */\n forInRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forInRight\n */\n forInRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forOwn\n interface LoDashStatic {\n /**\n * Iterates over own enumerable properties of an object invoking iteratee for each property. The iteratee is\n * bound to thisArg and invoked with three arguments: (value, key, object). Iteratee functions may exit\n * iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forOwn(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forOwn\n */\n forOwn(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forOwn\n */\n forOwn(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forOwn\n */\n forOwn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forOwn\n */\n forOwn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forOwnRight\n interface LoDashStatic {\n /**\n * This method is like _.forOwn except that it iterates over properties of object in the opposite order.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forOwnRight(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.functions\n interface LoDashStatic {\n /**\n * Creates an array of function property names from own enumerable properties\n * of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to inspect.\n * @returns {Array} Returns the new array of property names.\n * @example\n *\n * function Foo() {\n * this.a = _.constant('a');\n * this.b = _.constant('b');\n * }\n *\n * Foo.prototype.c = _.constant('c');\n *\n * _.functions(new Foo);\n * // => ['a', 'b']\n */\n functions(object: any): string[];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.functions\n */\n functions(): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.functions\n */\n functions(): _.LoDashExplicitArrayWrapper;\n }\n\n //_.functionsIn\n interface LoDashStatic {\n /**\n * Creates an array of function property names from own and inherited\n * enumerable properties of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to inspect.\n * @returns {Array} Returns the new array of property names.\n * @example\n *\n * function Foo() {\n * this.a = _.constant('a');\n * this.b = _.constant('b');\n * }\n *\n * Foo.prototype.c = _.constant('c');\n *\n * _.functionsIn(new Foo);\n * // => ['a', 'b', 'c']\n */\n functionsIn(object: any): string[];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.functionsIn\n */\n functionsIn(): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.functionsIn\n */\n functionsIn(): _.LoDashExplicitArrayWrapper;\n }\n\n //_.get\n interface LoDashStatic {\n /**\n * Gets the property value at path of object. If the resolved value is undefined the defaultValue is used\n * in its place.\n *\n * @param object The object to query.\n * @param path The path of the property to get.\n * @param defaultValue The value returned if the resolved value is undefined.\n * @return Returns the resolved value.\n */\n get(\n object: TObject,\n path: Many,\n defaultValue?: TResult\n ): TResult;\n\n /**\n * @see _.get\n */\n get(\n object: any,\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n //_.has\n interface LoDashStatic {\n /**\n * Checks if `path` is a direct property of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @param {Array|string} path The path to check.\n * @returns {boolean} Returns `true` if `path` exists, else `false`.\n * @example\n *\n * var object = { 'a': { 'b': { 'c': 3 } } };\n * var other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });\n *\n * _.has(object, 'a');\n * // => true\n *\n * _.has(object, 'a.b.c');\n * // => true\n *\n * _.has(object, ['a', 'b', 'c']);\n * // => true\n *\n * _.has(other, 'a');\n * // => false\n */\n has(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.has\n */\n has(path: Many): boolean;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.has\n */\n has(path: Many): LoDashExplicitWrapper;\n }\n\n //_.hasIn\n interface LoDashStatic {\n /**\n * Checks if `path` is a direct or inherited property of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @param {Array|string} path The path to check.\n * @returns {boolean} Returns `true` if `path` exists, else `false`.\n * @example\n *\n * var object = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });\n *\n * _.hasIn(object, 'a');\n * // => true\n *\n * _.hasIn(object, 'a.b.c');\n * // => true\n *\n * _.hasIn(object, ['a', 'b', 'c']);\n * // => true\n *\n * _.hasIn(object, 'b');\n * // => false\n */\n hasIn(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.hasIn\n */\n hasIn(path: Many): boolean;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.hasIn\n */\n hasIn(path: Many): LoDashExplicitWrapper;\n }\n\n //_.invert\n interface LoDashStatic {\n /**\n * Creates an object composed of the inverted keys and values of object. If object contains duplicate values,\n * subsequent values overwrite property assignments of previous values unless multiValue is true.\n *\n * @param object The object to invert.\n * @param multiValue Allow multiple values per key.\n * @return Returns the new inverted object.\n */\n invert(\n object: T,\n multiValue?: boolean\n ): TResult;\n\n /**\n * @see _.invert\n */\n invert(\n object: Object,\n multiValue?: boolean\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invert\n */\n invert(multiValue?: boolean): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invert\n */\n invert(multiValue?: boolean): LoDashExplicitObjectWrapper;\n }\n\n //_.inverBy\n type InvertByIterator = (value: T) => any;\n\n interface LoDashStatic {\n /**\n * This method is like _.invert except that the inverted object is generated from the results of running each\n * element of object through iteratee. The corresponding inverted value of each inverted key is an array of\n * keys responsible for generating the inverted value. The iteratee is invoked with one argument: (value).\n *\n * @param object The object to invert.\n * @param interatee The iteratee invoked per element.\n * @return Returns the new inverted object.\n */\n invertBy(\n object: Object,\n interatee?: InvertByIterator|string\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: _.Dictionary|_.NumericDictionary,\n interatee?: InvertByIterator|string\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: Object,\n interatee?: W\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: _.Dictionary,\n interatee?: W\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.keys\n interface LoDashStatic {\n /**\n * Creates an array of the own enumerable property names of object.\n *\n * Note: Non-object values are coerced to objects. See the ES spec for more details.\n *\n * @param object The object to query.\n * @return Returns the array of property names.\n */\n keys(object?: any): string[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keys\n */\n keys(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keys\n */\n keys(): LoDashExplicitArrayWrapper;\n }\n\n //_.keysIn\n interface LoDashStatic {\n /**\n * Creates an array of the own and inherited enumerable property names of object.\n *\n * Note: Non-object values are coerced to objects.\n *\n * @param object The object to query.\n * @return An array of property names.\n */\n keysIn(object?: any): string[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keysIn\n */\n keysIn(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keysIn\n */\n keysIn(): LoDashExplicitArrayWrapper;\n }\n\n //_.mapKeys\n interface LoDashStatic {\n /**\n * The opposite of _.mapValues; this method creates an object with the same values as object and keys generated\n * by running each own enumerable property of object through iteratee.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new mapped object.\n */\n mapKeys(\n object: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: List|Dictionary | null | undefined,\n iteratee?: TObject\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.mapValues\n interface LoDashStatic {\n /**\n * Creates an object with the same keys as object and values generated by running each own\n * enumerable property of object through iteratee. The iteratee function is bound to thisArg\n * and invoked with three arguments: (value, key, object).\n *\n * If a property name is provided iteratee the created \"_.property\" style callback returns\n * the property value of the given element.\n *\n * If a value is also provided for thisArg the creted \"_.matchesProperty\" style callback returns\n * true for elements that have a matching property value, else false;.\n *\n * If an object is provided for iteratee the created \"_.matches\" style callback returns true\n * for elements that have the properties of the given object, else false.\n *\n * @param {Object} object The object to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.\n * @param {Object} [thisArg] The `this` binding of `iteratee`.\n * @return {Object} Returns the new mapped object.\n */\n mapValues(obj: Dictionary | null | undefined, callback: ObjectIterator): Dictionary;\n mapValues(obj: Dictionary | null | undefined, where: Dictionary): Dictionary;\n mapValues(obj: T | null | undefined, pluck: string): TMapped;\n mapValues(obj: T | null | undefined, callback: ObjectIterator): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.mapValues\n * TValue is the type of the property values of T.\n * TResult is the type output by the ObjectIterator function\n */\n mapValues(callback: ObjectIterator): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the property specified by pluck.\n * T should be a Dictionary>\n */\n mapValues(pluck: string): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the properties of each object in the values of T\n * T should be a Dictionary>\n */\n mapValues(where: Dictionary): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.mapValues\n * TValue is the type of the property values of T.\n * TResult is the type output by the ObjectIterator function\n */\n mapValues(callback: ObjectIterator): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the property specified by pluck.\n * T should be a Dictionary>\n */\n mapValues(pluck: string): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the properties of each object in the values of T\n * T should be a Dictionary>\n */\n mapValues(where: Dictionary): LoDashExplicitObjectWrapper;\n }\n\n //_.merge\n interface LoDashStatic {\n /**\n * Recursively merges own and inherited enumerable properties of source\n * objects into the destination object, skipping source properties that resolve\n * to `undefined`. Array and plain object properties are merged recursively.\n * Other objects and value types are overridden by assignment. Source objects\n * are applied from left to right. Subsequent sources overwrite property\n * assignments of previous sources.\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * var users = {\n * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]\n * };\n *\n * var ages = {\n * 'data': [{ 'age': 36 }, { 'age': 40 }]\n * };\n *\n * _.merge(users, ages);\n * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }\n */\n merge(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.merge\n */\n merge(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.merge\n */\n merge(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ...otherArgs: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.merge\n */\n merge(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ...otherArgs: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.mergeWith\n type MergeWithCustomizer = (value: any, srcValue: any, key?: string, object?: Object, source?: Object) => any;\n\n interface LoDashStatic {\n /**\n * This method is like `_.merge` except that it accepts `customizer` which\n * is invoked to produce the merged values of the destination and source\n * properties. If `customizer` returns `undefined` merging is handled by the\n * method instead. The `customizer` is invoked with seven arguments:\n * (objValue, srcValue, key, object, source, stack).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} customizer The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * if (_.isArray(objValue)) {\n * return objValue.concat(srcValue);\n * }\n * }\n *\n * var object = {\n * 'fruits': ['apple'],\n * 'vegetables': ['beet']\n * };\n *\n * var other = {\n * 'fruits': ['banana'],\n * 'vegetables': ['carrot']\n * };\n *\n * _.merge(object, other, customizer);\n * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }\n */\n mergeWith(\n object: TObject,\n source: TSource,\n customizer: MergeWithCustomizer\n ): TObject & TSource;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source: TSource,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n ...otherArgs: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n //_.omit\n interface LoDashStatic {\n /**\n * The opposite of `_.pick`; this method creates an object composed of the\n * own and inherited enumerable properties of `object` that are not omitted.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {...(string|string[])} [props] The property names to omit, specified\n * individually or in arrays..\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.omit(object, ['a', 'c']);\n * // => { 'b': '2' }\n */\n\n omit(\n object: T | null | undefined,\n ...predicate: Array>\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.omit\n */\n omit(\n ...predicate: Array>\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.omit\n */\n omit(\n ...predicate: Array>\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.omitBy\n interface LoDashStatic {\n /**\n * The opposite of `_.pickBy`; this method creates an object composed of the\n * own and inherited enumerable properties of `object` that `predicate`\n * doesn't return truthy for.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.omitBy(object, _.isNumber);\n * // => { 'b': '2' }\n */\n omitBy(\n object: T | null | undefined,\n predicate: ObjectIterator\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.omitBy\n */\n omitBy(\n predicate: ObjectIterator\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.omitBy\n */\n omitBy(\n predicate: ObjectIterator\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.pick\n interface LoDashStatic {\n /**\n * Creates an object composed of the picked `object` properties.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {...(string|string[])} [props] The property names to pick, specified\n * individually or in arrays.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.pick(object, ['a', 'c']);\n * // => { 'a': 1, 'c': 3 }\n */\n pick(\n object: T | null | undefined,\n ...predicate: Array>\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.pick\n */\n pick(\n ...predicate: Array>\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.pick\n */\n pick(\n ...predicate: Array>\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.pickBy\n interface LoDashStatic {\n /**\n * Creates an object composed of the `object` properties `predicate` returns\n * truthy for. The predicate is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.pickBy(object, _.isNumber);\n * // => { 'a': 1, 'c': 3 }\n */\n pickBy(\n object: T | null | undefined,\n predicate?: ObjectIterator\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.pickBy\n */\n pickBy(\n predicate?: ObjectIterator\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.pickBy\n */\n pickBy(\n predicate?: ObjectIterator\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.result\n interface LoDashStatic {\n /**\n * This method is like _.get except that if the resolved value is a function it’s invoked with the this binding\n * of its parent object and its result is returned.\n *\n * @param object The object to query.\n * @param path The path of the property to resolve.\n * @param defaultValue The value returned if the resolved value is undefined.\n * @return Returns the resolved value.\n */\n result(\n object: TObject,\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n\n /**\n * @see _.result\n */\n result(\n object: any,\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n //_.set\n interface LoDashStatic {\n /**\n * Sets the value at path of object. If a portion of path doesn’t exist it’s created. Arrays are created for\n * missing index properties while objects are created for all other missing properties. Use _.setWith to\n * customize path creation.\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @return Returns object.\n */\n set(\n object: Object,\n path: Many,\n value: any\n ): TResult;\n\n /**\n * @see _.set\n */\n set(\n object: Object,\n path: Many,\n value: V\n ): TResult;\n\n /**\n * @see _.set\n */\n set(\n object: O,\n path: Many,\n value: V\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: any\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: V\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: any\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: V\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.setWith\n type SetWithCustomizer = (nsValue: any, key: string, nsObject: T) => any;\n\n interface LoDashStatic {\n /**\n * This method is like _.set except that it accepts customizer which is invoked to produce the objects of\n * path. If customizer returns undefined path creation is handled by the method instead. The customizer is\n * invoked with three arguments: (nsValue, key, nsObject).\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @parem customizer The function to customize assigned values.\n * @return Returns object.\n */\n setWith(\n object: Object,\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): TResult;\n\n /**\n * @see _.setWith\n */\n setWith(\n object: Object,\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): TResult;\n\n /**\n * @see _.setWith\n */\n setWith(\n object: O,\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.toPairs\n interface LoDashStatic {\n /**\n * Creates an array of own enumerable key-value pairs for object.\n *\n * @param object The object to query.\n * @return Returns the new array of key-value pairs.\n */\n toPairs(object?: T): [string, any][];\n\n toPairs(object?: T): [string, TResult][];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.toPairs\n */\n toPairs(): LoDashImplicitArrayWrapper<[string, TResult]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.toPairs\n */\n toPairs(): LoDashExplicitArrayWrapper<[string, TResult]>;\n }\n\n //_.toPairsIn\n interface LoDashStatic {\n /**\n * Creates an array of own and inherited enumerable key-value pairs for object.\n *\n * @param object The object to query.\n * @return Returns the new array of key-value pairs.\n */\n toPairsIn(object?: T): [string, any][];\n\n toPairsIn(object?: T): [string, TResult][];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.toPairsIn\n */\n toPairsIn(): LoDashImplicitArrayWrapper<[string, TResult]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.toPairsIn\n */\n toPairsIn(): LoDashExplicitArrayWrapper<[string, TResult]>;\n }\n\n //_.transform\n interface LoDashStatic {\n /**\n * An alternative to _.reduce; this method transforms object to a new accumulator object which is the result of\n * running each of its own enumerable properties through iteratee, with each invocation potentially mutating\n * the accumulator object. The iteratee is bound to thisArg and invoked with four arguments: (accumulator,\n * value, key, object). Iteratee functions may exit iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param accumulator The custom accumulator value.\n * @param thisArg The this binding of iteratee.\n * @return Returns the accumulated value.\n */\n transform(\n object: T[],\n iteratee?: MemoVoidArrayIterator,\n accumulator?: TResult[]\n ): TResult[];\n\n /**\n * @see _.transform\n */\n transform(\n object: T[],\n iteratee?: MemoVoidArrayIterator>,\n accumulator?: Dictionary\n ): Dictionary;\n\n /**\n * @see _.transform\n */\n transform(\n object: Dictionary,\n iteratee?: MemoVoidDictionaryIterator>,\n accumulator?: Dictionary\n ): Dictionary;\n\n /**\n * @see _.transform\n */\n transform(\n object: Dictionary,\n iteratee?: MemoVoidDictionaryIterator,\n accumulator?: TResult[]\n ): TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidArrayIterator,\n accumulator?: TResult[]\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidArrayIterator>,\n accumulator?: Dictionary\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidDictionaryIterator>,\n accumulator?: Dictionary\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidDictionaryIterator,\n accumulator?: TResult[]\n ): LoDashImplicitArrayWrapper;\n }\n\n //_.unset\n interface LoDashStatic {\n /**\n * Removes the property at path of object.\n *\n * Note: This method mutates object.\n *\n * @param object The object to modify.\n * @param path The path of the property to unset.\n * @return Returns true if the property is deleted, else false.\n */\n unset(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.unset\n */\n unset(path: Many): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.unset\n */\n unset(path: Many): LoDashExplicitWrapper;\n }\n\n //_.update\n interface LoDashStatic {\n /**\n * This method is like _.set except that accepts updater to produce the value to set. Use _.updateWith to\n * customize path creation. The updater is invoked with one argument: (value).\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param updater The function to produce the updated value.\n * @return Returns object.\n */\n update(\n object: Object,\n path: Many,\n updater: Function\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: Object,\n path: Many,\n updater: U\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: O,\n path: Many,\n updater: Function\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: O,\n path: Many,\n updater: U\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: any\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: U\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: any\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: U\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.values\n interface LoDashStatic {\n /**\n * Creates an array of the own enumerable property values of object.\n *\n * @param object The object to query.\n * @return Returns an array of property values.\n */\n values(object?: Dictionary|NumericDictionary|List | null | undefined): T[];\n\n /**\n * @see _.values\n */\n values(object?: any): T[];\n }\n\n interface LoDashImplicitStringWrapper {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n //_.valuesIn\n interface LoDashStatic {\n /**\n * Creates an array of the own and inherited enumerable property values of object.\n *\n * @param object The object to query.\n * @return Returns the array of property values.\n */\n valuesIn(object?: Dictionary): T[];\n\n /**\n * @see _.valuesIn\n */\n valuesIn(object?: any): T[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.valuesIn\n */\n valuesIn(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.valuesIn\n */\n valuesIn(): LoDashExplicitArrayWrapper;\n }\n\n /**********\n * String *\n **********/\n\n //_.camelCase\n interface LoDashStatic {\n /**\n * Converts string to camel case.\n *\n * @param string The string to convert.\n * @return Returns the camel cased string.\n */\n camelCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.camelCase\n */\n camelCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.camelCase\n */\n camelCase(): LoDashExplicitWrapper;\n }\n\n //_.capitalize\n interface LoDashStatic {\n /**\n * Converts the first character of string to upper case and the remaining to lower case.\n *\n * @param string The string to capitalize.\n * @return Returns the capitalized string.\n */\n capitalize(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.capitalize\n */\n capitalize(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.capitalize\n */\n capitalize(): LoDashExplicitWrapper;\n }\n\n //_.deburr\n interface LoDashStatic {\n /**\n * Deburrs string by converting latin-1 supplementary letters to basic latin letters and removing combining\n * diacritical marks.\n *\n * @param string The string to deburr.\n * @return Returns the deburred string.\n */\n deburr(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.deburr\n */\n deburr(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.deburr\n */\n deburr(): LoDashExplicitWrapper;\n }\n\n //_.endsWith\n interface LoDashStatic {\n /**\n * Checks if string ends with the given target string.\n *\n * @param string The string to search.\n * @param target The string to search for.\n * @param position The position to search from.\n * @return Returns true if string ends with target, else false.\n */\n endsWith(\n string?: string,\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.endsWith\n */\n endsWith(\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.endsWith\n */\n endsWith(\n target?: string,\n position?: number\n ): LoDashExplicitWrapper;\n }\n\n // _.escape\n interface LoDashStatic {\n /**\n * Converts the characters \"&\", \"<\", \">\", '\"', \"'\", and \"`\" in string to their corresponding HTML entities.\n *\n * Note: No other characters are escaped. To escape additional characters use a third-party library like he.\n *\n * hough the \">\" character is escaped for symmetry, characters like \">\" and \"/\" don’t need escaping in HTML\n * and have no special meaning unless they're part of a tag or unquoted attribute value. See Mathias Bynens’s\n * article (under \"semi-related fun fact\") for more details.\n *\n * Backticks are escaped because in IE < 9, they can break out of attribute values or HTML comments. See #59,\n * #102, #108, and #133 of the HTML5 Security Cheatsheet for more details.\n *\n * When working with HTML you should always quote attribute values to reduce XSS vectors.\n *\n * @param string The string to escape.\n * @return Returns the escaped string.\n */\n escape(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.escape\n */\n escape(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.escape\n */\n escape(): LoDashExplicitWrapper;\n }\n\n // _.escapeRegExp\n interface LoDashStatic {\n /**\n * Escapes the RegExp special characters \"^\", \"$\", \"\\\", \".\", \"*\", \"+\", \"?\", \"(\", \")\", \"[\", \"]\",\n * \"{\", \"}\", and \"|\" in string.\n *\n * @param string The string to escape.\n * @return Returns the escaped string.\n */\n escapeRegExp(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.escapeRegExp\n */\n escapeRegExp(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.escapeRegExp\n */\n escapeRegExp(): LoDashExplicitWrapper;\n }\n\n //_.kebabCase\n interface LoDashStatic {\n /**\n * Converts string to kebab case.\n *\n * @param string The string to convert.\n * @return Returns the kebab cased string.\n */\n kebabCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.kebabCase\n */\n kebabCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.kebabCase\n */\n kebabCase(): LoDashExplicitWrapper;\n }\n\n //_.lowerCase\n interface LoDashStatic {\n /**\n * Converts `string`, as space separated words, to lower case.\n *\n * @param string The string to convert.\n * @return Returns the lower cased string.\n */\n lowerCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.lowerCase\n */\n lowerCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.lowerCase\n */\n lowerCase(): LoDashExplicitWrapper;\n }\n\n //_.lowerFirst\n interface LoDashStatic {\n /**\n * Converts the first character of `string` to lower case.\n *\n * @param string The string to convert.\n * @return Returns the converted string.\n */\n lowerFirst(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.lowerFirst\n */\n lowerFirst(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.lowerFirst\n */\n lowerFirst(): LoDashExplicitWrapper;\n }\n\n //_.pad\n interface LoDashStatic {\n /**\n * Pads string on the left and right sides if it’s shorter than length. Padding characters are truncated if\n * they can’t be evenly divided by length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n pad(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.pad\n */\n pad(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.pad\n */\n pad(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.padEnd\n interface LoDashStatic {\n /**\n * Pads string on the right side if it’s shorter than length. Padding characters are truncated if they exceed\n * length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n padEnd(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.padEnd\n */\n padEnd(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.padEnd\n */\n padEnd(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.padStart\n interface LoDashStatic {\n /**\n * Pads string on the left side if it’s shorter than length. Padding characters are truncated if they exceed\n * length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n padStart(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.padStart\n */\n padStart(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.padStart\n */\n padStart(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.parseInt\n interface LoDashStatic {\n /**\n * Converts string to an integer of the specified radix. If radix is undefined or 0, a radix of 10 is used\n * unless value is a hexadecimal, in which case a radix of 16 is used.\n *\n * Note: This method aligns with the ES5 implementation of parseInt.\n *\n * @param string The string to convert.\n * @param radix The radix to interpret value by.\n * @return Returns the converted integer.\n */\n parseInt(\n string: string,\n radix?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.parseInt\n */\n parseInt(radix?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.parseInt\n */\n parseInt(radix?: number): LoDashExplicitWrapper;\n }\n\n //_.repeat\n interface LoDashStatic {\n /**\n * Repeats the given string n times.\n *\n * @param string The string to repeat.\n * @param n The number of times to repeat the string.\n * @return Returns the repeated string.\n */\n repeat(\n string?: string,\n n?: number\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.repeat\n */\n repeat(n?: number): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.repeat\n */\n repeat(n?: number): LoDashExplicitWrapper;\n }\n\n //_.replace\n interface LoDashStatic {\n /**\n * Replaces matches for pattern in string with replacement.\n *\n * Note: This method is based on String#replace.\n *\n * @param string\n * @param pattern\n * @param replacement\n * @return Returns the modified string.\n */\n replace(\n string: string,\n pattern: RegExp|string,\n replacement: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n }\n\n //_.snakeCase\n interface LoDashStatic {\n /**\n * Converts string to snake case.\n *\n * @param string The string to convert.\n * @return Returns the snake cased string.\n */\n snakeCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.snakeCase\n */\n snakeCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.snakeCase\n */\n snakeCase(): LoDashExplicitWrapper;\n }\n\n //_.split\n interface LoDashStatic {\n /**\n * Splits string by separator.\n *\n * Note: This method is based on String#split.\n *\n * @param string\n * @param separator\n * @param limit\n * @return Returns the new array of string segments.\n */\n split(\n string: string,\n separator?: RegExp|string,\n limit?: number\n ): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.split\n */\n split(\n separator?: RegExp|string,\n limit?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.split\n */\n split(\n separator?: RegExp|string,\n limit?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.startCase\n interface LoDashStatic {\n /**\n * Converts string to start case.\n *\n * @param string The string to convert.\n * @return Returns the start cased string.\n */\n startCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.startCase\n */\n startCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.startCase\n */\n startCase(): LoDashExplicitWrapper;\n }\n\n //_.startsWith\n interface LoDashStatic {\n /**\n * Checks if string starts with the given target string.\n *\n * @param string The string to search.\n * @param target The string to search for.\n * @param position The position to search from.\n * @return Returns true if string starts with target, else false.\n */\n startsWith(\n string?: string,\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.startsWith\n */\n startsWith(\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.startsWith\n */\n startsWith(\n target?: string,\n position?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.template\n interface TemplateOptions extends TemplateSettings {\n /**\n * The sourceURL of the template's compiled source.\n */\n sourceURL?: string;\n }\n\n interface TemplateExecutor {\n (data?: Object): string;\n source: string;\n }\n\n interface LoDashStatic {\n /**\n * Creates a compiled template function that can interpolate data properties in \"interpolate\" delimiters,\n * HTML-escape interpolated data properties in \"escape\" delimiters, and execute JavaScript in \"evaluate\"\n * delimiters. Data properties may be accessed as free variables in the template. If a setting object is\n * provided it takes precedence over _.templateSettings values.\n *\n * Note: In the development build _.template utilizes\n * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier\n * debugging.\n *\n * For more information on precompiling templates see\n * [lodash's custom builds documentation](https://lodash.com/custom-builds).\n *\n * For more information on Chrome extension sandboxes see\n * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).\n *\n * @param string The template string.\n * @param options The options object.\n * @param options.escape The HTML \"escape\" delimiter.\n * @param options.evaluate The \"evaluate\" delimiter.\n * @param options.imports An object to import into the template as free variables.\n * @param options.interpolate The \"interpolate\" delimiter.\n * @param options.sourceURL The sourceURL of the template's compiled source.\n * @param options.variable The data object variable name.\n * @return Returns the compiled template function.\n */\n template(\n string: string,\n options?: TemplateOptions\n ): TemplateExecutor;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.template\n */\n template(options?: TemplateOptions): TemplateExecutor;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.template\n */\n template(options?: TemplateOptions): LoDashExplicitObjectWrapper;\n }\n\n //_.toLower\n interface LoDashStatic {\n /**\n * Converts `string`, as a whole, to lower case.\n *\n * @param string The string to convert.\n * @return Returns the lower cased string.\n */\n toLower(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toLower\n */\n toLower(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toLower\n */\n toLower(): LoDashExplicitWrapper;\n }\n\n //_.toUpper\n interface LoDashStatic {\n /**\n * Converts `string`, as a whole, to upper case.\n *\n * @param string The string to convert.\n * @return Returns the upper cased string.\n */\n toUpper(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toUpper\n */\n toUpper(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toUpper\n */\n toUpper(): LoDashExplicitWrapper;\n }\n\n //_.trim\n interface LoDashStatic {\n /**\n * Removes leading and trailing whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trim(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trim\n */\n trim(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trim\n */\n trim(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.trimEnd\n interface LoDashStatic {\n /**\n * Removes trailing whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trimEnd(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trimEnd\n */\n trimEnd(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trimEnd\n */\n trimEnd(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.trimStart\n interface LoDashStatic {\n /**\n * Removes leading whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trimStart(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trimStart\n */\n trimStart(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trimStart\n */\n trimStart(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.truncate\n interface TruncateOptions {\n /** The maximum string length. */\n length?: number;\n /** The string to indicate text is omitted. */\n omission?: string;\n /** The separator pattern to truncate to. */\n separator?: string|RegExp;\n }\n\n interface LoDashStatic {\n /**\n * Truncates string if it’s longer than the given maximum string length. The last characters of the truncated\n * string are replaced with the omission string which defaults to \"…\".\n *\n * @param string The string to truncate.\n * @param options The options object or maximum string length.\n * @return Returns the truncated string.\n */\n truncate(\n string?: string,\n options?: TruncateOptions\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.truncate\n */\n truncate(options?: TruncateOptions): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.truncate\n */\n truncate(options?: TruncateOptions): LoDashExplicitWrapper;\n }\n\n //_.unescape\n interface LoDashStatic {\n /**\n * The inverse of _.escape; this method converts the HTML entities &, <, >, ", ', and `\n * in string to their corresponding characters.\n *\n * Note: No other HTML entities are unescaped. To unescape additional HTML entities use a third-party library\n * like he.\n *\n * @param string The string to unescape.\n * @return Returns the unescaped string.\n */\n unescape(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.unescape\n */\n unescape(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.unescape\n */\n unescape(): LoDashExplicitWrapper;\n }\n\n //_.upperCase\n interface LoDashStatic {\n /**\n * Converts `string`, as space separated words, to upper case.\n *\n * @param string The string to convert.\n * @return Returns the upper cased string.\n */\n upperCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.upperCase\n */\n upperCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.upperCase\n */\n upperCase(): LoDashExplicitWrapper;\n }\n\n //_.upperFirst\n interface LoDashStatic {\n /**\n * Converts the first character of `string` to upper case.\n *\n * @param string The string to convert.\n * @return Returns the converted string.\n */\n upperFirst(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.upperFirst\n */\n upperFirst(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.upperFirst\n */\n upperFirst(): LoDashExplicitWrapper;\n }\n\n //_.words\n interface LoDashStatic {\n /**\n * Splits `string` into an array of its words.\n *\n * @param string The string to inspect.\n * @param pattern The pattern to match words.\n * @return Returns the words of `string`.\n */\n words(\n string?: string,\n pattern?: string|RegExp\n ): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.words\n */\n words(pattern?: string|RegExp): string[];\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.words\n */\n words(pattern?: string|RegExp): LoDashExplicitArrayWrapper;\n }\n\n /***********\n * Utility *\n ***********/\n\n //_.attempt\n interface LoDashStatic {\n /**\n * Attempts to invoke func, returning either the result or the caught error object. Any additional arguments\n * are provided to func when it’s invoked.\n *\n * @param func The function to attempt.\n * @return Returns the func result or error object.\n */\n attempt(func: (...args: any[]) => TResult, ...args: any[]): TResult|Error;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.attempt\n */\n attempt(...args: any[]): TResult|Error;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.attempt\n */\n attempt(...args: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.constant\n interface LoDashStatic {\n /**\n * Creates a function that returns value.\n *\n * @param value The value to return from the new function.\n * @return Returns the new function.\n */\n constant(value: T): () => T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.constant\n */\n constant(): LoDashImplicitObjectWrapper<() => TResult>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.constant\n */\n constant(): LoDashExplicitObjectWrapper<() => TResult>;\n }\n\n //_.defaultTo\n interface LoDashStatic {\n /**\n * Checks `value` to determine whether a default value should be returned in\n * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,\n * or `undefined`.\n *\n * @param value The value to check.\n * @param defaultValue The default value.\n * @returns Returns the resolved value.\n */\n defaultTo(value: T | null | undefined, defaultValue: T): T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.defaultTo\n */\n defaultTo(value: TResult): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.defaultTo\n */\n defaultTo(value: TResult): LoDashExplicitObjectWrapper;\n }\n\n //_.identity\n interface LoDashStatic {\n /**\n * This method returns the first argument provided to it.\n *\n * @param value Any value.\n * @return Returns value.\n */\n identity(value: T): T;\n\n /**\n * @see _.identity\n */\n identity(): undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.identity\n */\n identity(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.identity\n */\n identity(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TWrapper;\n }\n\n //_.iteratee\n interface LoDashStatic {\n /**\n * Creates a function that invokes `func` with the arguments of the created\n * function. If `func` is a property name the created callback returns the\n * property value for a given element. If `func` is an object the created\n * callback returns `true` for elements that contain the equivalent object properties, otherwise it returns `false`.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {*} [func=_.identity] The value to convert to a callback.\n * @returns {Function} Returns the callback.\n * @example\n *\n * var users = [\n * { 'user': 'barney', 'age': 36 },\n * { 'user': 'fred', 'age': 40 }\n * ];\n *\n * // create custom iteratee shorthands\n * _.iteratee = _.wrap(_.iteratee, function(callback, func) {\n * var p = /^(\\S+)\\s*([<>])\\s*(\\S+)$/.exec(func);\n * return !p ? callback(func) : function(object) {\n * return (p[2] == '>' ? object[p[1]] > p[3] : object[p[1]] < p[3]);\n * };\n * });\n *\n * _.filter(users, 'age > 36');\n * // => [{ 'user': 'fred', 'age': 40 }]\n */\n iteratee(\n func: TFunction\n ): TFunction;\n\n /**\n * @see _.iteratee\n */\n iteratee(\n func: string\n ): (object: any) => TResult;\n\n /**\n * @see _.iteratee\n */\n iteratee(\n func: Object\n ): (object: any) => boolean;\n\n /**\n * @see _.iteratee\n */\n iteratee(): (value: TResult) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(object: any) => boolean>;\n\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(...args: any[]) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(object: any) => boolean>;\n\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(...args: any[]) => TResult>;\n }\n\n //_.matches\n interface LoDashStatic {\n /**\n * Creates a function that performs a deep comparison between a given object and source, returning true if the\n * given object has equivalent property values, else false.\n *\n * Note: This method supports comparing arrays, booleans, Date objects, numbers, Object objects, regexes, and\n * strings. Objects are compared by their own, not inherited, enumerable properties. For comparing a single own\n * or inherited property value see _.matchesProperty.\n *\n * @param source The object of property values to match.\n * @return Returns the new function.\n */\n matches(source: T): (value: any) => boolean;\n\n /**\n * @see _.matches\n */\n matches(source: T): (value: V) => boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.matches\n */\n matches(): LoDashImplicitObjectWrapper<(value: V) => boolean>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.matches\n */\n matches(): LoDashExplicitObjectWrapper<(value: V) => boolean>;\n }\n\n //_.matchesProperty\n interface LoDashStatic {\n /**\n * Creates a function that compares the property value of path on a given object to value.\n *\n * Note: This method supports comparing arrays, booleans, Date objects, numbers, Object objects, regexes, and\n * strings. Objects are compared by their own, not inherited, enumerable properties.\n *\n * @param path The path of the property to get.\n * @param srcValue The value to match.\n * @return Returns the new function.\n */\n matchesProperty(\n path: Many,\n srcValue: T\n ): (value: any) => boolean;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n path: Many,\n srcValue: T\n ): (value: V) => boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashImplicitObjectWrapper<(value: any) => boolean>;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashImplicitObjectWrapper<(value: Value) => boolean>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashExplicitObjectWrapper<(value: any) => boolean>;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashExplicitObjectWrapper<(value: Value) => boolean>;\n }\n\n //_.method\n interface LoDashStatic {\n /**\n * Creates a function that invokes the method at path on a given object. Any additional arguments are provided\n * to the invoked method.\n *\n * @param path The path of the method to invoke.\n * @param args The arguments to invoke the method with.\n * @return Returns the new function.\n */\n method(\n path: string|StringRepresentable[],\n ...args: any[]\n ): (object: TObject) => TResult;\n\n /**\n * @see _.method\n */\n method(\n path: string|StringRepresentable[],\n ...args: any[]\n ): (object: any) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n //_.methodOf\n interface LoDashStatic {\n /**\n * The opposite of _.method; this method creates a function that invokes the method at a given path on object.\n * Any additional arguments are provided to the invoked method.\n *\n * @param object The object to query.\n * @param args The arguments to invoke the method with.\n * @return Returns the new function.\n */\n methodOf(\n object: TObject,\n ...args: any[]\n ): (path: Many) => TResult;\n\n /**\n * @see _.methodOf\n */\n methodOf(\n object: {},\n ...args: any[]\n ): (path: Many) => TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.methodOf\n */\n methodOf(\n ...args: any[]\n ): LoDashImplicitObjectWrapper<(path: Many) => TResult>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.methodOf\n */\n methodOf(\n ...args: any[]\n ): LoDashExplicitObjectWrapper<(path: Many) => TResult>;\n }\n\n //_.mixin\n interface MixinOptions {\n chain?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Adds all own enumerable function properties of a source object to the destination object. If object is a\n * function then methods are added to its prototype as well.\n *\n * Note: Use _.runInContext to create a pristine lodash function to avoid conflicts caused by modifying\n * the original.\n *\n * @param object The destination object.\n * @param source The object of functions to add.\n * @param options The options object.\n * @param options.chain Specify whether the functions added are chainable.\n * @return Returns object.\n */\n mixin(\n object: TObject,\n source: Dictionary,\n options?: MixinOptions\n ): TResult;\n\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mixin\n */\n mixin(\n options?: MixinOptions\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.mixin\n */\n mixin(\n options?: MixinOptions\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.noConflict\n interface LoDashStatic {\n /**\n * Reverts the _ variable to its previous value and returns a reference to the lodash function.\n *\n * @return Returns the lodash function.\n */\n noConflict(): typeof _;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.noConflict\n */\n noConflict(): typeof _;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.noConflict\n */\n noConflict(): LoDashExplicitObjectWrapper;\n }\n\n //_.noop\n interface LoDashStatic {\n /**\n * A no-operation function that returns undefined regardless of the arguments it receives.\n *\n * @return undefined\n */\n noop(...args: any[]): void;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.noop\n */\n noop(...args: any[]): void;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.noop\n */\n noop(...args: any[]): _.LoDashExplicitWrapper;\n }\n\n //_.nthArg\n interface LoDashStatic {\n /**\n * Creates a function that returns its nth argument.\n *\n * @param n The index of the argument to return.\n * @return Returns the new function.\n */\n nthArg(n?: number): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.nthArg\n */\n nthArg(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.nthArg\n */\n nthArg(): LoDashExplicitObjectWrapper;\n }\n\n //_.over\n interface LoDashStatic {\n /**\n * Creates a function that invokes iteratees with the arguments provided to the created function and returns\n * their results.\n *\n * @param iteratees The iteratees to invoke.\n * @return Returns the new function.\n */\n over(...iteratees: Array>): (...args: any[]) => TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n //_.overEvery\n interface LoDashStatic {\n /**\n * Creates a function that checks if all of the predicates return truthy when invoked with the arguments\n * provided to the created function.\n *\n * @param predicates The predicates to check.\n * @return Returns the new function.\n */\n overEvery(...predicates: Array>): (...args: any[]) => boolean;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n //_.overSome\n interface LoDashStatic {\n /**\n * Creates a function that checks if any of the predicates return truthy when invoked with the arguments\n * provided to the created function.\n *\n * @param predicates The predicates to check.\n * @return Returns the new function.\n */\n overSome(...predicates: Array>): (...args: any[]) => boolean;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n //_.property\n interface LoDashStatic {\n /**\n * Creates a function that returns the property value at path on a given object.\n *\n * @param path The path of the property to get.\n * @return Returns the new function.\n */\n property(path: Many): (obj: TObj) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.property\n */\n property(): LoDashImplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.property\n */\n property(): LoDashImplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.property\n */\n property(): LoDashExplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.property\n */\n property(): LoDashExplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n //_.propertyOf\n interface LoDashStatic {\n /**\n * The opposite of _.property; this method creates a function that returns the property value at a given path\n * on object.\n *\n * @param object The object to query.\n * @return Returns the new function.\n */\n propertyOf(object: T): (path: Many) => any;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.propertyOf\n */\n propertyOf(): LoDashImplicitObjectWrapper<(path: Many) => any>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.propertyOf\n */\n propertyOf(): LoDashExplicitObjectWrapper<(path: Many) => any>;\n }\n\n //_.range\n interface LoDashStatic {\n /**\n * Creates an array of numbers (positive and/or negative) progressing from start up to, but not including, end.\n * If end is not specified it’s set to start with start then set to 0. If end is less than start a zero-length\n * range is created unless a negative step is specified.\n *\n * @param start The start of the range.\n * @param end The end of the range.\n * @param step The value to increment or decrement by.\n * @return Returns a new range array.\n */\n range(\n start: number,\n end: number,\n step?: number\n ): number[];\n\n /**\n * @see _.range\n */\n range(\n end: number,\n step?: number\n ): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.range\n */\n range(\n end?: number,\n step?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.range\n */\n range(\n end?: number,\n step?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.rangeRight\n interface LoDashStatic {\n /**\n * This method is like `_.range` except that it populates values in\n * descending order.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {number} [start=0] The start of the range.\n * @param {number} end The end of the range.\n * @param {number} [step=1] The value to increment or decrement by.\n * @returns {Array} Returns the new array of numbers.\n * @example\n *\n * _.rangeRight(4);\n * // => [3, 2, 1, 0]\n *\n * _.rangeRight(-4);\n * // => [-3, -2, -1, 0]\n *\n * _.rangeRight(1, 5);\n * // => [4, 3, 2, 1]\n *\n * _.rangeRight(0, 20, 5);\n * // => [15, 10, 5, 0]\n *\n * _.rangeRight(0, -4, -1);\n * // => [-3, -2, -1, 0]\n *\n * _.rangeRight(1, 4, 0);\n * // => [1, 1, 1]\n *\n * _.rangeRight(0);\n * // => []\n */\n rangeRight(\n start: number,\n end: number,\n step?: number\n ): number[];\n\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end: number,\n step?: number\n ): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end?: number,\n step?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end?: number,\n step?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.runInContext\n interface LoDashStatic {\n /**\n * Create a new pristine lodash function using the given context object.\n *\n * @param context The context object.\n * @return Returns a new lodash function.\n */\n runInContext(context?: Object): typeof _;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.runInContext\n */\n runInContext(): typeof _;\n }\n\n // _.stubArray\n interface LoDashStatic {\n /**\n * This method returns a new empty array.\n *\n * @returns Returns the new empty array.\n */\n stubArray(): any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubArray\n */\n stubArray(): any[];\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubArray\n */\n stubArray(): _.LoDashExplicitArrayWrapper;\n }\n\n // _.stubFalse\n interface LoDashStatic {\n /**\n * This method returns `false`.\n *\n * @returns Returns `false`.\n */\n stubFalse(): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubFalse\n */\n stubFalse(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubFalse\n */\n stubFalse(): _.LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns a new empty object.\n *\n * @returns Returns the new empty object.\n */\n stubObject(): Object;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubObject\n */\n stubObject(): Object;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubObject\n */\n stubObject(): _.LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns an empty string.\n *\n * @returns Returns the empty string.\n */\n stubString(): string;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubString\n */\n stubString(): string;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubString\n */\n stubString(): _.LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns `true`.\n *\n * @returns Returns `true`.\n */\n stubTrue(): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubTrue\n */\n stubTrue(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubTrue\n */\n stubTrue(): _.LoDashExplicitWrapper;\n }\n\n //_.times\n interface LoDashStatic {\n /**\n * Invokes the iteratee function n times, returning an array of the results of each invocation. The iteratee\n * is invoked with one argument; (index).\n *\n * @param n The number of times to invoke iteratee.\n * @param iteratee The function invoked per iteration.\n * @return Returns the array of results.\n */\n times(\n n: number,\n iteratee: (num: number) => TResult\n ): TResult[];\n\n /**\n * @see _.times\n */\n times(n: number): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.times\n */\n times(\n iteratee: (num: number) => TResult\n ): TResult[];\n\n /**\n * @see _.times\n */\n times(): number[];\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.times\n */\n times(\n iteratee: (num: number) => TResult\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.times\n */\n times(): LoDashExplicitArrayWrapper;\n }\n\n //_.toPath\n interface LoDashStatic {\n /**\n * Converts `value` to a property path array.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {*} value The value to convert.\n * @returns {Array} Returns the new property path array.\n * @example\n *\n * _.toPath('a.b.c');\n * // => ['a', 'b', 'c']\n *\n * _.toPath('a[0].b.c');\n * // => ['a', '0', 'b', 'c']\n *\n * var path = ['a', 'b', 'c'],\n * newPath = _.toPath(path);\n *\n * console.log(newPath);\n * // => ['a', 'b', 'c']\n *\n * console.log(path === newPath);\n * // => false\n */\n toPath(value: any): string[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toPath\n */\n toPath(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toPath\n */\n toPath(): LoDashExplicitWrapper;\n }\n\n //_.uniqueId\n interface LoDashStatic {\n /**\n * Generates a unique ID. If prefix is provided the ID is appended to it.\n *\n * @param prefix The value to prefix the ID with.\n * @return Returns the unique ID.\n */\n uniqueId(prefix?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniqueId\n */\n uniqueId(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniqueId\n */\n uniqueId(): LoDashExplicitWrapper;\n }\n\n type ListIterator = (value: T, index: number, collection: List) => TResult;\n\n type ListIteratorTypeGuard = (value: T, index: number, collection: List) => value is S;\n\n type DictionaryIterator = (value: T, key: string, collection: Dictionary) => TResult;\n\n type DictionaryIteratorTypeGuard = (value: T, key: string, collection: Dictionary) => value is S;\n\n type NumericDictionaryIterator = (value: T, key: number, collection: Dictionary) => TResult;\n\n type ObjectIterator = (element: T, key: string, collection: any) => TResult;\n\n type StringIterator = (char: string, index: number, string: string) => TResult;\n\n type MemoVoidIterator = (prev: TResult, curr: T, indexOrKey: any, list: T[]) => void;\n\n type MemoIterator = (prev: TResult, curr: T, indexOrKey: any, list: T[]) => TResult;\n\n type MemoVoidArrayIterator = (acc: TResult, curr: T, index: number, arr: T[]) => void;\n type MemoVoidDictionaryIterator = (acc: TResult, curr: T, key: string, dict: Dictionary) => void;\n\n /** Common interface between Arrays and jQuery objects */\n type List = ArrayLike;\n\n interface Dictionary {\n [index: string]: T;\n }\n\n interface NumericDictionary {\n [index: number]: T;\n }\n\n interface StringRepresentable {\n toString(): string;\n }\n\n interface Cancelable {\n cancel(): void;\n flush(): void;\n }\n}\n\n// Backward compatibility with --target es5\ndeclare global {\n interface Set { }\n interface Map { }\n interface WeakSet { }\n interface WeakMap { }\n}\n" } + ); + verifyAfterPartialOrCompleteNpmInstall(2); + + filesAndFoldersToAdd.push( + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/src/scheduler" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/src/util" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/symbol" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/testing" }, + { "path": "/a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041", "content": "{\n \"_args\": [\n [\n {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\"\n ]\n ],\n \"_from\": \"rxjs@>=5.4.2 <6.0.0\",\n \"_id\": \"rxjs@5.4.3\",\n \"_inCache\": true,\n \"_location\": \"/rxjs\",\n \"_nodeVersion\": \"7.7.2\",\n \"_npmOperationalInternal\": {\n \"host\": \"s3://npm-registry-packages\",\n \"tmp\": \"tmp/rxjs-5.4.3.tgz_1502407898166_0.6800217325799167\"\n },\n \"_npmUser\": {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"_npmVersion\": \"5.3.0\",\n \"_phantomChildren\": {},\n \"_requested\": {\n \"raw\": \"rxjs@^5.4.2\",\n \"scope\": null,\n \"escapedName\": \"rxjs\",\n \"name\": \"rxjs\",\n \"rawSpec\": \"^5.4.2\",\n \"spec\": \">=5.4.2 <6.0.0\",\n \"type\": \"range\"\n },\n \"_requiredBy\": [\n \"/\"\n ],\n \"_resolved\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\",\n \"_shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"_shrinkwrap\": null,\n \"_spec\": \"rxjs@^5.4.2\",\n \"_where\": \"C:\\\\Users\\\\shkamat\\\\Desktop\\\\app\",\n \"author\": {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/ReactiveX/RxJS/issues\"\n },\n \"config\": {\n \"commitizen\": {\n \"path\": \"cz-conventional-changelog\"\n }\n },\n \"contributors\": [\n {\n \"name\": \"Ben Lesh\",\n \"email\": \"ben@benlesh.com\"\n },\n {\n \"name\": \"Paul Taylor\",\n \"email\": \"paul.e.taylor@me.com\"\n },\n {\n \"name\": \"Jeff Cross\",\n \"email\": \"crossj@google.com\"\n },\n {\n \"name\": \"Matthew Podwysocki\",\n \"email\": \"matthewp@microsoft.com\"\n },\n {\n \"name\": \"OJ Kwon\",\n \"email\": \"kwon.ohjoong@gmail.com\"\n },\n {\n \"name\": \"Andre Staltz\",\n \"email\": \"andre@staltz.com\"\n }\n ],\n \"dependencies\": {\n \"symbol-observable\": \"^1.0.1\"\n },\n \"description\": \"Reactive Extensions for modern JavaScript\",\n \"devDependencies\": {\n \"babel-polyfill\": \"^6.23.0\",\n \"benchmark\": \"^2.1.0\",\n \"benchpress\": \"2.0.0-beta.1\",\n \"chai\": \"^3.5.0\",\n \"color\": \"^0.11.1\",\n \"colors\": \"1.1.2\",\n \"commitizen\": \"^2.8.6\",\n \"coveralls\": \"^2.11.13\",\n \"cz-conventional-changelog\": \"^1.2.0\",\n \"danger\": \"^1.1.0\",\n \"doctoc\": \"^1.0.0\",\n \"escape-string-regexp\": \"^1.0.5 \",\n \"esdoc\": \"^0.4.7\",\n \"eslint\": \"^3.8.0\",\n \"fs-extra\": \"^2.1.2\",\n \"get-folder-size\": \"^1.0.0\",\n \"glob\": \"^7.0.3\",\n \"gm\": \"^1.22.0\",\n \"google-closure-compiler-js\": \"^20170218.0.0\",\n \"gzip-size\": \"^3.0.0\",\n \"http-server\": \"^0.9.0\",\n \"husky\": \"^0.13.3\",\n \"lint-staged\": \"3.2.5\",\n \"lodash\": \"^4.15.0\",\n \"madge\": \"^1.4.3\",\n \"markdown-doctest\": \"^0.9.1\",\n \"minimist\": \"^1.2.0\",\n \"mkdirp\": \"^0.5.1\",\n \"mocha\": \"^3.0.2\",\n \"mocha-in-sauce\": \"0.0.1\",\n \"npm-run-all\": \"^4.0.2\",\n \"npm-scripts-info\": \"^0.3.4\",\n \"nyc\": \"^10.2.0\",\n \"opn-cli\": \"^3.1.0\",\n \"platform\": \"^1.3.1\",\n \"promise\": \"^7.1.1\",\n \"protractor\": \"^3.1.1\",\n \"rollup\": \"0.36.3\",\n \"rollup-plugin-inject\": \"^2.0.0\",\n \"rollup-plugin-node-resolve\": \"^2.0.0\",\n \"rx\": \"latest\",\n \"rxjs\": \"latest\",\n \"shx\": \"^0.2.2\",\n \"sinon\": \"^2.1.0\",\n \"sinon-chai\": \"^2.9.0\",\n \"source-map-support\": \"^0.4.0\",\n \"tslib\": \"^1.5.0\",\n \"tslint\": \"^4.4.2\",\n \"typescript\": \"~2.0.6\",\n \"typings\": \"^2.0.0\",\n \"validate-commit-msg\": \"^2.14.0\",\n \"watch\": \"^1.0.1\",\n \"webpack\": \"^1.13.1\",\n \"xmlhttprequest\": \"1.8.0\"\n },\n \"directories\": {},\n \"dist\": {\n \"integrity\": \"sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==\",\n \"shasum\": \"0758cddee6033d68e0fd53676f0f3596ce3d483f\",\n \"tarball\": \"https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz\"\n },\n \"engines\": {\n \"npm\": \">=2.0.0\"\n },\n \"homepage\": \"https://github.com/ReactiveX/RxJS\",\n \"keywords\": [\n \"Rx\",\n \"RxJS\",\n \"ReactiveX\",\n \"ReactiveExtensions\",\n \"Streams\",\n \"Observables\",\n \"Observable\",\n \"Stream\",\n \"ES6\",\n \"ES2015\"\n ],\n \"license\": \"Apache-2.0\",\n \"lint-staged\": {\n \"*.@(js)\": [\n \"eslint --fix\",\n \"git add\"\n ],\n \"*.@(ts)\": [\n \"tslint --fix\",\n \"git add\"\n ]\n },\n \"main\": \"Rx.js\",\n \"maintainers\": [\n {\n \"name\": \"blesh\",\n \"email\": \"ben@benlesh.com\"\n }\n ],\n \"name\": \"rxjs\",\n \"optionalDependencies\": {},\n \"readme\": \"ERROR: No README data found!\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+ssh://git@github.com/ReactiveX/RxJS.git\"\n },\n \"scripts-info\": {\n \"info\": \"List available script\",\n \"build_all\": \"Build all packages (ES6, CJS, UMD) and generate packages\",\n \"build_cjs\": \"Build CJS package with clean up existing build, copy source into dist\",\n \"build_es6\": \"Build ES6 package with clean up existing build, copy source into dist\",\n \"build_closure_core\": \"Minify Global core build using closure compiler\",\n \"build_global\": \"Build Global package, then minify build\",\n \"build_perf\": \"Build CJS & Global build, run macro performance test\",\n \"build_test\": \"Build CJS package & test spec, execute mocha test runner\",\n \"build_cover\": \"Run lint to current code, build CJS & test spec, execute test coverage\",\n \"build_docs\": \"Build ES6 & global package, create documentation using it\",\n \"build_spec\": \"Build test specs\",\n \"check_circular_dependencies\": \"Check codebase has circular dependencies\",\n \"clean_spec\": \"Clean up existing test spec build output\",\n \"clean_dist_cjs\": \"Clean up existing CJS package output\",\n \"clean_dist_es6\": \"Clean up existing ES6 package output\",\n \"clean_dist_global\": \"Clean up existing Global package output\",\n \"commit\": \"Run git commit wizard\",\n \"compile_dist_cjs\": \"Compile codebase into CJS module\",\n \"compile_module_es6\": \"Compile codebase into ES6\",\n \"cover\": \"Execute test coverage\",\n \"lint_perf\": \"Run lint against performance test suite\",\n \"lint_spec\": \"Run lint against test spec\",\n \"lint_src\": \"Run lint against source\",\n \"lint\": \"Run lint against everything\",\n \"perf\": \"Run macro performance benchmark\",\n \"perf_micro\": \"Run micro performance benchmark\",\n \"test_mocha\": \"Execute mocha test runner against existing test spec build\",\n \"test_browser\": \"Execute mocha test runner on browser against existing test spec build\",\n \"test\": \"Clean up existing test spec build, build test spec and execute mocha test runner\",\n \"tests2png\": \"Generate marble diagram image from test spec\",\n \"watch\": \"Watch codebase, trigger compile when source code changes\"\n },\n \"typings\": \"Rx.d.ts\",\n \"version\": \"5.4.3\"\n}\n" } + ); + verifyAfterPartialOrCompleteNpmInstall(0); + + // remove /a/b/node_modules/.staging/rxjs-22375c61/package.json.2252192041 + filesAndFoldersToAdd.length--; + // and add few more folders/files + filesAndFoldersToAdd.push( + { "path": "/a/b/node_modules/symbol-observable" }, + { "path": "/a/b/node_modules/@types" }, + { "path": "/a/b/node_modules/@types/lodash" }, + { "path": "/a/b/node_modules/lodash" }, + { "path": "/a/b/node_modules/rxjs" }, + { "path": "/a/b/node_modules/typescript" }, + { "path": "/a/b/node_modules/.bin" } + ); + verifyAfterPartialOrCompleteNpmInstall(0); + + forEach(filesAndFoldersToAdd, f => { + f.path = f.path + .replace("/a/b/node_modules/.staging", "/a/b/node_modules") + .replace(/[\-\.][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w][\d\w]/g, ""); + }); + + const lodashIndexPath = "/a/b/node_modules/@types/lodash/index.d.ts"; + projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)); + watchedModuleLocations.length = watchedModuleLocations.indexOf(lodashIndexPath); + // npm installation complete, timeout after reload fs + timeoutAfterReloadFs = true; + verifyAfterPartialOrCompleteNpmInstall(2); + + function verifyAfterPartialOrCompleteNpmInstall(timeoutQueueLengthWhenRunningTimeouts: number) { + host.reloadFS(projectFiles.concat(otherFiles, filesAndFoldersToAdd)); + if (timeoutAfterReloadFs) { + host.checkTimeoutQueueLengthAndRun(timeoutQueueLengthWhenRunningTimeouts); + } + else { + host.checkTimeoutQueueLength(2); + } + verifyProject(); + } + + function verifyProject() { + checkNumberOfConfiguredProjects(projectService, 1); + + const project = projectService.configuredProjects.get(tsconfigJson.path); + const projectFilePaths = map(projectFiles, f => f.path); + checkProjectActualFiles(project, projectFilePaths); + + const filesWatched = filter(projectFilePaths, p => p !== app.path).concat(watchedModuleLocations); + checkWatchedFiles(host, filesWatched); + checkWatchedDirectories(host, [appFolder], /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + } + + function getNodeModulesWatchedDirectories(path: string, module: string): string[] { + const nodeModulesDir = combinePaths(path, "node_modules/"); + const parentDir = getDirectoryPath(path); + const parentNodeModules = parentDir !== path ? getNodeModulesWatchedDirectories(parentDir, module) : []; + return [ + `${nodeModulesDir}${module}.ts`, + `${nodeModulesDir}${module}.tsx`, + `${nodeModulesDir}${module}.d.ts`, + `${nodeModulesDir}${module}/index.ts`, + `${nodeModulesDir}${module}/index.tsx`, + `${nodeModulesDir}${module}/index.d.ts`, + `${nodeModulesDir}@types/${module}.d.ts`, + `${nodeModulesDir}@types/${module}/index.d.ts`, + `${nodeModulesDir}@types/${module}/package.json`, + `${nodeModulesDir}${module}.js`, + `${nodeModulesDir}${module}.jsx`, + `${nodeModulesDir}${module}/package.json`, + `${nodeModulesDir}${module}/index.js`, + `${nodeModulesDir}${module}/index.jsx`, + ].concat(parentNodeModules); + } + } + + it("timeouts occur inbetween installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ true); + }); + + it("timeout occurs after installation", () => { + verifyNpmInstall(/*timeoutDuringPartialInstallation*/ false); + }); }); }); } From e71123857c1a4088f34a8a6d236b256ef2ffdc86 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 15 Aug 2017 14:50:52 -0700 Subject: [PATCH 074/144] Add api in builder to get changed files and use it to send project changed event --- src/compiler/builder.ts | 220 ++++++++++-------- .../unittests/tsserverProjectSystem.ts | 4 +- src/server/editorServices.ts | 46 ++-- src/server/project.ts | 15 +- src/server/protocol.ts | 23 ++ src/server/session.ts | 58 +++-- 6 files changed, 224 insertions(+), 142 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 04d520cb228..d96c3a547c0 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -18,6 +18,13 @@ namespace ts { text: string; } + export interface ChangedProgramFiles { + /** Minimal set of list of files that require emit */ + readonly filesToEmit: ReadonlyArray; + /** File paths of source files changed/added/removed or affected by changed files */ + readonly changedFiles: ReadonlyArray; + } + export interface Builder { /** * This is the callback when file infos in the builder are updated @@ -25,8 +32,15 @@ namespace ts { onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution): void; getFilesAffectedBy(program: Program, path: Path): string[]; emitFile(program: Program, path: Path): EmitOutput; + + /** Emit the changed files and clear the cache of the changed files */ emitChangedFiles(program: Program): EmitOutputDetailed[]; + /** Get the changed files since last query and then clear the cache of changed files */ + getChangedProgramFiles(program: Program): ChangedProgramFiles; + /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[]; + + /** Called to reset the status of the builder */ clear(): void; } @@ -73,16 +87,18 @@ namespace ts { ): Builder { let isModuleEmit: boolean | undefined; // Last checked shape signature for the file info - type FileInfo = { version: string; signature: string; }; - let fileInfos: Map; + type FileInfo = { fileName: string; version: string; signature: string; }; + const fileInfos = createMap(); const semanticDiagnosticsPerFile = createMap(); - let changedFilesSinceLastEmit: Map; + /** The map has key by source file's path that has been changed */ + const changedFileNames = createMap(); let emitHandler: EmitHandler; return { onProgramUpdateGraph, getFilesAffectedBy, emitFile, emitChangedFiles, + getChangedProgramFiles, getSemanticDiagnostics, clear }; @@ -92,13 +108,11 @@ namespace ts { if (isModuleEmit !== currentIsModuleEmit) { isModuleEmit = currentIsModuleEmit; emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); - fileInfos = undefined; + fileInfos.clear(); semanticDiagnosticsPerFile.clear(); } - - changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap(); mutateMap( - fileInfos || (fileInfos = createMap()), + fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), { // Add new file info @@ -111,27 +125,26 @@ namespace ts { ); } - function registerChangedFile(path: Path) { - changedFilesSinceLastEmit.set(path, true); + function registerChangedFile(path: Path, fileName: string) { + changedFileNames.set(path, fileName); // All changed files need to re-evaluate its semantic diagnostics semanticDiagnosticsPerFile.delete(path); } function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { - registerChangedFile(sourceFile.path); + registerChangedFile(sourceFile.path, sourceFile.fileName); emitHandler.addScriptInfo(program, sourceFile); - return { version: sourceFile.version, signature: undefined }; + return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined }; } - function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) { - registerChangedFile(path); + function removeExistingFileInfo(path: Path, existingFileInfo: FileInfo) { + registerChangedFile(path, existingFileInfo.fileName); emitHandler.removeScriptInfo(path); } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) { - registerChangedFile(sourceFile.path); - semanticDiagnosticsPerFile.delete(sourceFile.path); + registerChangedFile(sourceFile.path, sourceFile.fileName); existingInfo.version = sourceFile.version; emitHandler.updateScriptInfo(program, sourceFile); } @@ -154,7 +167,7 @@ namespace ts { const sourceFile = program.getSourceFile(path); const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : []; - const info = fileInfos && fileInfos.get(path); + const info = fileInfos.get(path); if (!info || !updateShapeSignature(program, sourceFile, info)) { return singleFileResult; } @@ -165,44 +178,61 @@ namespace ts { function emitFile(program: Program, path: Path) { ensureProgramGraph(program); - if (!fileInfos || !fileInfos.has(path)) { + if (!fileInfos.has(path)) { return { outputFiles: [], emitSkipped: true }; } return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false); } - function emitChangedFiles(program: Program): EmitOutputDetailed[] { - ensureProgramGraph(program); - const result: EmitOutputDetailed[] = []; - if (changedFilesSinceLastEmit) { - const seenFiles = createMap(); - changedFilesSinceLastEmit.forEach((__value, path: Path) => { - const affectedFiles = getFilesAffectedBy(program, path); - for (const file of affectedFiles) { - if (!seenFiles.has(file)) { - const sourceFile = program.getSourceFile(file); - seenFiles.set(file, sourceFile); - if (sourceFile) { - // Any affected file shouldnt have the cached diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); + function enumerateChangedFilesSet( + program: Program, + onChangedFile: (fileName: string) => void, + onAffectedFile: (fileName: string, sourceFile: SourceFile) => void + ) { + changedFileNames.forEach((fileName, path) => { + onChangedFile(fileName); + const affectedFiles = getFilesAffectedBy(program, path as Path); + for (const file of affectedFiles) { + onAffectedFile(file, program.getSourceFile(file)); + } + }); + } - const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed; - result.push(emitOutput); + function enumerateChangedFilesEmitOutput( + program: Program, + emitOnlyDtsFiles: boolean, + onChangedFile: (fileName: string) => void, + onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void + ) { + const seenFiles = createMap(); + enumerateChangedFilesSet(program, onChangedFile, (fileName, sourceFile) => { + if (!seenFiles.has(fileName)) { + seenFiles.set(fileName, sourceFile); + if (sourceFile) { + // Any affected file shouldnt have the cached diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); - // mark all the emitted source files as seen - if (emitOutput.emittedSourceFiles) { - for (const file of emitOutput.emittedSourceFiles) { - seenFiles.set(file.fileName, file); - } - } + const emitOutput = getEmitOutput(program, sourceFile, emitOnlyDtsFiles, /*isDetailed*/ true) as EmitOutputDetailed; + onEmitOutput(emitOutput, sourceFile); + + // mark all the emitted source files as seen + if (emitOutput.emittedSourceFiles) { + for (const file of emitOutput.emittedSourceFiles) { + seenFiles.set(file.fileName, file); } } } - }); + } + }); + } - changedFilesSinceLastEmit = undefined; - } + function emitChangedFiles(program: Program): EmitOutputDetailed[] { + ensureProgramGraph(program); + const result: EmitOutputDetailed[] = []; + enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ false, + /*onChangedFile*/ noop, emitOutput => result.push(emitOutput)); + changedFileNames.clear(); return result; } @@ -210,17 +240,11 @@ namespace ts { ensureProgramGraph(program); // Ensure that changed files have cleared their respective - if (changedFilesSinceLastEmit) { - changedFilesSinceLastEmit.forEach((__value, path: Path) => { - const affectedFiles = getFilesAffectedBy(program, path); - for (const file of affectedFiles) { - const sourceFile = program.getSourceFile(file); - if (sourceFile) { - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - } - }); - } + enumerateChangedFilesSet(program, /*onChangedFile*/ noop, (_affectedFileName, sourceFile) => { + if (sourceFile) { + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + }); let diagnostics: Diagnostic[]; for (const sourceFile of program.getSourceFiles()) { @@ -240,11 +264,36 @@ namespace ts { return diagnostics || emptyArray; } + function getChangedProgramFiles(program: Program): ChangedProgramFiles { + ensureProgramGraph(program); + + let filesToEmit: string[]; + let changedFiles: string[]; + enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ true, + // All the changed files are required to get diagnostics + changedFileName => addFileForDiagnostics(changedFileName), + // Emitted file is for emit as well as diagnostic + (_emitOutput, sourceFile) => { + (filesToEmit || (filesToEmit = [])).push(sourceFile.fileName); + addFileForDiagnostics(sourceFile.fileName); + }); + changedFileNames.clear(); + return { + filesToEmit: filesToEmit || emptyArray, + changedFiles: changedFiles || emptyArray + }; + + function addFileForDiagnostics(fileName: string) { + (changedFiles || (changedFiles = [])).push(fileName); + } + } + function clear() { isModuleEmit = undefined; emitHandler = undefined; - fileInfos = undefined; + fileInfos.clear(); semanticDiagnosticsPerFile.clear(); + changedFileNames.clear(); } /** @@ -287,9 +336,7 @@ namespace ts { } /** - * Gets the referenced files for a file from the program - * @param program - * @param path + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true */ function getReferencedFiles(program: Program, sourceFile: SourceFile): Map { const referencedFiles = createMap(); @@ -371,55 +418,36 @@ namespace ts { function getModuleEmitHandler(): EmitHandler { const references = createMap>(); - const referencedBy = createMultiMap(); return { - addScriptInfo: (program, sourceFile) => { - const refs = createMap(); - references.set(sourceFile.path, refs); - setReferences(program, sourceFile, refs); - }, + addScriptInfo: setReferences, removeScriptInfo, - updateScriptInfo: (program, sourceFile) => setReferences(program, sourceFile, references.get(sourceFile.path)), + updateScriptInfo: setReferences, getFilesAffectedByUpdatedShape }; - function setReferences(program: Program, sourceFile: SourceFile, existingReferences: Map) { - const path = sourceFile.path; - mutateMap( - // Existing references - existingReferences, - // Updated references - getReferencedFiles(program, sourceFile), - { - // Creating new Reference: as sourceFile references file with path 'key' - // in other words source file (path) is referenced by 'key' - createNewValue: (key): true => { referencedBy.add(key, path); return true; }, - // Remove existing reference by entry: source file doesnt reference file 'key' any more - // in other words source file (path) is not referenced by 'key' - onDeleteValue: (key, _existingValue) => { referencedBy.remove(key, path); } - } - ); + function setReferences(program: Program, sourceFile: SourceFile) { + references.set(sourceFile.path, getReferencedFiles(program, sourceFile)); } - function removeScriptInfo(path: Path) { + function removeScriptInfo(removedFilePath: Path) { // Remove existing references - references.forEach((_value, key) => { - referencedBy.remove(key, path); - }); - references.delete(path); - - // Delete the entry and add files referencing this file, as chagned files too - const referencedByPaths = referencedBy.get(path); - if (referencedByPaths) { - for (const path of referencedByPaths) { - registerChangedFile(path); + references.forEach((referencesInFile, filePath) => { + if (referencesInFile.has(removedFilePath)) { + // add files referencing the removedFilePath, as changed files too + const referencedByInfo = fileInfos.get(filePath); + if (referencedByInfo) { + registerChangedFile(filePath as Path, referencedByInfo.fileName); + } } - referencedBy.delete(path); - } + }); + // Delete the entry for the removed file path + references.delete(removedFilePath); } - function getReferencedByPaths(path: Path) { - return referencedBy.get(path) || []; + function getReferencedByPaths(referencedFilePath: Path) { + return mapDefinedIter(references.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); } function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] { @@ -444,7 +472,7 @@ namespace ts { // Start with the paths this file was referenced by const path = sourceFile.path; setSeenFileName(path, sourceFile); - const queue = getReferencedByPaths(path).slice(); + const queue = getReferencedByPaths(path); while (queue.length > 0) { const currentPath = queue.pop(); if (!seenFileNamesMap.has(currentPath)) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 58c2b3a614d..3833089743b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -125,7 +125,7 @@ namespace ts.projectSystem { this.events.push(event); } - checkEventCountOfType(eventType: "context" | "configFileDiag", expectedCount: number) { + checkEventCountOfType(eventType: "configFileDiag", expectedCount: number) { const eventsOfType = filter(this.events, e => e.eventName === eventType); assert.equal(eventsOfType.length, expectedCount, `The actual event counts of type ${eventType} is ${eventsOfType.length}, while expected ${expectedCount}`); } @@ -2001,7 +2001,7 @@ namespace ts.projectSystem { const session = createSession(host, { canUseEvents: true, eventHandler: e => { - if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ContextEvent || e.eventName === server.ProjectInfoTelemetryEvent) { + if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectChangedEvent || e.eventName === server.ProjectInfoTelemetryEvent) { return; } assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 884ee518a30..385ade0b816 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -10,14 +10,14 @@ namespace ts.server { export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; - export const ContextEvent = "context"; + export const ProjectChangedEvent = "projectChanged"; export const ConfigFileDiagEvent = "configFileDiag"; export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectInfoTelemetryEvent = "projectInfo"; - export interface ContextEvent { - eventName: typeof ContextEvent; - data: { project: Project; fileName: NormalizedPath }; + export interface ProjectChangedEvent { + eventName: typeof ProjectChangedEvent; + data: { project: Project; filesToEmit: string[]; changedFiles: string[] }; } export interface ConfigFileDiagEvent { @@ -77,7 +77,7 @@ namespace ts.server { readonly dts: number; } - export type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; + export type ProjectServiceEvent = ProjectChangedEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; export interface ProjectServiceEventHandler { (event: ProjectServiceEvent): void; @@ -492,9 +492,32 @@ namespace ts.server { if (this.pendingProjectUpdates.delete(projectName)) { project.updateGraph(); } + // Send the update event to notify about the project changes + this.sendProjectChangedEvent(project); }); } + private sendProjectChangedEvent(project: Project) { + if (project.isClosed() || !this.eventHandler || !project.languageServiceEnabled) { + return; + } + + const { filesToEmit, changedFiles } = project.getChangedFiles(); + if (changedFiles.length === 0) { + return; + } + + const event: ProjectChangedEvent = { + eventName: ProjectChangedEvent, + data: { + project, + filesToEmit: filesToEmit as string[], + changedFiles: changedFiles as string[] + } + }; + this.eventHandler(event); + } + /* @internal */ delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { this.delayUpdateProjectGraph(project); @@ -678,19 +701,6 @@ namespace ts.server { // update projects to make sure that set of referenced files is correct this.delayUpdateProjectGraphs(containingProjects); - - // TODO: (sheetalkamat) Someway to send this event so that error checks are updated? - // if (!this.eventHandler) { - // return; - // } - - // for (const openFile of this.openFiles) { - // const event: ContextEvent = { - // eventName: ContextEvent, - // data: { project: openFile.getDefaultProject(), fileName: openFile.fileName } - // }; - // this.eventHandler(event); - // } } } diff --git a/src/server/project.ts b/src/server/project.ts index b35298b2938..cce0fc5b510 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -133,7 +133,7 @@ namespace ts.server { /*@internal*/ lsHost: LSHost; - builder: Builder; + private builder: Builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -307,6 +307,12 @@ namespace ts.server { return !emitSkipped; } + getChangedFiles() { + Debug.assert(this.languageServiceEnabled); + this.ensureBuilder(); + return this.builder.getChangedProgramFiles(this.program); + } + getProjectVersion() { return this.projectStateVersion.toString(); } @@ -392,6 +398,10 @@ namespace ts.server { this.languageService = undefined; } + isClosed() { + return this.lsHost === undefined; + } + getCompilerOptions() { return this.compilerOptions; } @@ -635,8 +645,9 @@ namespace ts.server { // update builder only if language service is enabled // otherwise tell it to drop its internal state + // Note we are retaining builder so we can send events for project change if (this.builder) { - if (this.languageServiceEnabled && this.compileOnSaveEnabled) { + if (this.languageServiceEnabled) { this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution); } else { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f50c8731126..3cc8555032f 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2040,6 +2040,29 @@ namespace ts.server.protocol { languageServiceEnabled: boolean; } + export type ProjectChangedEventName = "projectChanged"; + export interface ProjectStructureChangedEvent extends Event { + event: ProjectChangedEventName; + body: ProjectChangedEventBody; + } + + export interface ProjectChangedEventBody { + /** + * Project name that has changes + */ + projectName: string; + + /** + * Minimum set of file names to emit + */ + fileNamesToEmit: string[]; + + /** + * List of files that have changed/added/removed or could have been affected by the changed files + */ + changedFiles: string[]; + } + /** * Arguments for reload request. */ diff --git a/src/server/session.ts b/src/server/session.ts index d4fc22425a0..4b42aaba96a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -332,14 +332,18 @@ namespace ts.server { private defaultEventHandler(event: ProjectServiceEvent) { switch (event.eventName) { - case ContextEvent: - const { project, fileName } = event.data; - this.projectService.logger.info(`got context event, updating diagnostics for ${fileName}`); - this.errorCheck.startNew(next => this.updateErrorCheck(next, [{ fileName, project }], 100)); + case ProjectChangedEvent: + const { project, filesToEmit, changedFiles } = event.data; + this.projectChangedEvent(project, filesToEmit, changedFiles); break; case ConfigFileDiagEvent: - const { triggerFile, configFileName, diagnostics } = event.data; - this.configFileDiagnosticEvent(triggerFile, configFileName, diagnostics); + const { triggerFile, configFileName: configFile, diagnostics } = event.data; + const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true)); + this.event({ + triggerFile, + configFile, + diagnostics: bakedDiags + }, "configFileDiag"); break; case ProjectLanguageServiceStateEvent: { const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState"; @@ -360,6 +364,24 @@ namespace ts.server { } } + private projectChangedEvent(project: Project, fileNamesToEmit: string[], changedFiles: string[]): void { + this.projectService.logger.info(`got project changed event, updating diagnostics for ${changedFiles}`); + if (changedFiles.length) { + const checkList = this.createCheckList(changedFiles, project); + + // For now only queue error checking for open files. We can change this to include non open files as well + this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true)); + + + // Send project changed event + this.event({ + projectName: project.getProjectName(), + changedFiles, + fileNamesToEmit + }, "projectChanged"); + } + } + public logError(err: Error, cmd: string) { let msg = "Exception on executing command " + cmd; if (err.message) { @@ -381,21 +403,6 @@ namespace ts.server { this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine)); } - public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ReadonlyArray) { - const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true)); - const ev: protocol.ConfigFileDiagnosticEvent = { - seq: 0, - type: "event", - event: "configFileDiag", - body: { - triggerFile, - configFile, - diagnostics: bakedDiags - } - }; - this.send(ev); - } - public event(info: T, eventName: string) { const ev: protocol.Event = { seq: 0, @@ -1257,13 +1264,16 @@ namespace ts.server { } } - private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void { - const checkList = mapDefined(fileNames, uncheckedFileName => { + private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] { + return mapDefined(fileNames, uncheckedFileName => { const fileName = toNormalizedPath(uncheckedFileName); - const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true); + const project = defaultProject || this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true); return project && { fileName, project }; }); + } + private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void { + const checkList = this.createCheckList(fileNames); if (checkList.length > 0) { this.updateErrorCheck(next, checkList, delay); } From 3b85f3fbe215b401cc9eed0998107e1b9bcee8d5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 22 Aug 2017 16:03:55 -0700 Subject: [PATCH 075/144] Add tests to verify project changed event sent --- src/compiler/builder.ts | 18 +- .../unittests/tsserverProjectSystem.ts | 533 ++++++++++++++++++ 2 files changed, 542 insertions(+), 9 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index d96c3a547c0..4fb90ca7143 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -187,11 +187,11 @@ namespace ts { function enumerateChangedFilesSet( program: Program, - onChangedFile: (fileName: string) => void, + onChangedFile: (fileName: string, path: Path) => void, onAffectedFile: (fileName: string, sourceFile: SourceFile) => void ) { changedFileNames.forEach((fileName, path) => { - onChangedFile(fileName); + onChangedFile(fileName, path as Path); const affectedFiles = getFilesAffectedBy(program, path as Path); for (const file of affectedFiles) { onAffectedFile(file, program.getSourceFile(file)); @@ -202,7 +202,7 @@ namespace ts { function enumerateChangedFilesEmitOutput( program: Program, emitOnlyDtsFiles: boolean, - onChangedFile: (fileName: string) => void, + onChangedFile: (fileName: string, path: Path) => void, onEmitOutput: (emitOutput: EmitOutputDetailed, sourceFile: SourceFile) => void ) { const seenFiles = createMap(); @@ -268,23 +268,23 @@ namespace ts { ensureProgramGraph(program); let filesToEmit: string[]; - let changedFiles: string[]; + const changedFiles = createMap(); enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ true, // All the changed files are required to get diagnostics - changedFileName => addFileForDiagnostics(changedFileName), + (changedFileName, changedFilePath) => addFileForDiagnostics(changedFileName, changedFilePath), // Emitted file is for emit as well as diagnostic (_emitOutput, sourceFile) => { (filesToEmit || (filesToEmit = [])).push(sourceFile.fileName); - addFileForDiagnostics(sourceFile.fileName); + addFileForDiagnostics(sourceFile.fileName, sourceFile.path); }); changedFileNames.clear(); return { filesToEmit: filesToEmit || emptyArray, - changedFiles: changedFiles || emptyArray + changedFiles: arrayFrom(changedFiles.values()) }; - function addFileForDiagnostics(fileName: string) { - (changedFiles || (changedFiles = [])).push(fileName); + function addFileForDiagnostics(fileName: string, path: Path) { + changedFiles.set(path, fileName); } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 3833089743b..3c394da4e31 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4467,4 +4467,537 @@ namespace ts.projectSystem { }); }); }); + + describe("ProjectChangedEvent", () => { + function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { + assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); + const seen = createMap(); + forEach(actual, f => { + assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); + seen.set(f, true); + assert.isTrue(contains(expected, f), `${caption}: Expected not to contain ${f}. Actual: ${actual} Expected: ${expected}`); + }); + } + + function createVerifyInitialOpen(session: TestSession, verifyProjectChangedEventHandler: (events: server.ProjectChangedEvent[]) => void) { + return (file: FileOrFolder) => { + session.executeCommandSeq({ + command: server.CommandNames.Open, + arguments: { + file: file.path + } + }); + verifyProjectChangedEventHandler([]); + }; + } + + interface ProjectChangeEventVerifier { + session: TestSession; + verifyProjectChangedEventHandler(events: server.ProjectChangedEvent[]): void; + verifyInitialOpen(file: FileOrFolder): void; + } + + function verifyProjectChangedEvent(createSession: (host: TestServerHost) => ProjectChangeEventVerifier) { + it("when adding new file", () => { + const commonFile1: FileOrFolder = { + path: "/a/b/file1.ts", + content: "export var x = 10;" + }; + const commonFile2: FileOrFolder = { + path: "/a/b/file2.ts", + content: "export var y = 10;" + }; + const commonFile3: FileOrFolder = { + path: "/a/b/file3.ts", + content: "export var z = 10;" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, libFile, configFile]); + const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host, ); + const projectService = session.getProjectService(); + verifyInitialOpen(commonFile1); + + host.reloadFS([commonFile1, libFile, configFile, commonFile2]); + host.runQueuedTimeoutCallbacks(); + // Since this is first event + const project = projectService.configuredProjects.get(configFile.path); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles: [libFile.path, commonFile1.path, commonFile2.path], + filesToEmit: [commonFile1.path, commonFile2.path] + } + }]); + + host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]); + host.runQueuedTimeoutCallbacks(); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles: [commonFile3.path], + filesToEmit: [commonFile3.path] + } + }]); + }); + + describe("with --out or --outFile setting", () => { + function verifyEventWithOutSettings(compilerOptions: CompilerOptions = {}) { + const config: FileOrFolder = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions + }) + }; + + const f1: FileOrFolder = { + path: "/a/a.ts", + content: "export let x = 1" + }; + const f2: FileOrFolder = { + path: "/a/b.ts", + content: "export let y = 1" + }; + + const files = [f1, config, libFile]; + const host = createServerHost(files); + const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); + const projectService = session.getProjectService(); + verifyInitialOpen(f1); + + files.push(f2); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + + // Since this is first event + const project = projectService.configuredProjects.get(config.path); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles: [libFile.path, f1.path, f2.path], + filesToEmit: [f1.path, f2.path] + } + }]); + + f2.content = "export let x = 11"; + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles: [f2.path], + filesToEmit: [f2.path] + } + }]); + } + + it("when both options are not set", () => { + verifyEventWithOutSettings(); + }); + + it("when --out is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ out: outJs }); + }); + + it("when --outFile is set", () => { + const outJs = "/a/out.js"; + verifyEventWithOutSettings({ outFile: outJs }); + }); + }); + + describe("with modules and configured project", () => { + const file1Consumer1Path = "/a/b/file1Consumer1.ts"; + const moduleFile1Path = "/a/b/moduleFile1.ts"; + const configFilePath = "/a/b/tsconfig.json"; + type InitialStateParams = { + /** custom config file options */ + configObj?: any; + /** list of files emitted/changed on first update graph */ + firstCompilationEmitFiles?: string[]; + /** Additional files and folders to add */ + getAdditionalFileOrFolder?(): FileOrFolder[]; + /** initial list of files to reload in fs and first file in this list being the file to open */ + firstReloadFileList?: string[]; + }; + function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList, firstCompilationEmitFiles }: InitialStateParams = {}) { + const moduleFile1: FileOrFolder = { + path: moduleFile1Path, + content: "export function Foo() { };", + }; + + const file1Consumer1: FileOrFolder = { + path: file1Consumer1Path, + content: `import {Foo} from "./moduleFile1"; export var y = 10;`, + }; + + const file1Consumer2: FileOrFolder = { + path: "/a/b/file1Consumer2.ts", + content: `import {Foo} from "./moduleFile1"; let z = 10;`, + }; + + const moduleFile2: FileOrFolder = { + path: "/a/b/moduleFile2.ts", + content: `export var Foo4 = 10;`, + }; + + const globalFile3: FileOrFolder = { + path: "/a/b/globalFile3.ts", + content: `interface GlobalFoo { age: number }` + }; + + const additionalFiles = getAdditionalFileOrFolder ? getAdditionalFileOrFolder() : []; + const configFile = { + path: configFilePath, + content: JSON.stringify(configObj || { compilerOptions: {} }) + }; + + const files = [file1Consumer1, moduleFile1, file1Consumer2, moduleFile2, ...additionalFiles, globalFile3, libFile, configFile]; + + const filesToReload = firstReloadFileList && getFiles(firstReloadFileList) || files; + const host = createServerHost([filesToReload[0], configFile]); + + // Initial project creation + const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host); + const projectService = session.getProjectService(); + verifyInitialOpen(filesToReload[0]); + + // Since this is first event, it will have all the files + const firstFilesExpected = firstCompilationEmitFiles && getFiles(firstCompilationEmitFiles) || filesToReload; + verifyProjectChangedEvent(firstFilesExpected, filesToReload); + + return { + moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, + files, + updateContentOfOpenFile, + verifyProjectChangedEvent, + verifyAffectedAllFiles, + }; + + function getFiles(filelist: string[]) { + return map(filelist, getFile); + } + + function getFile(fileName: string) { + return find(files, file => file.path === fileName); + } + + function verifyAffectedAllFiles() { + verifyProjectChangedEvent(filter(files, f => f !== libFile)); + } + + function verifyProjectChangedEvent(filesToEmit: FileOrFolder[], filesToReload?: FileOrFolder[], additionalChangedFiles?: FileOrFolder[]) { + const changedFiles = mapDefined(additionalChangedFiles ? filesToEmit.concat(additionalChangedFiles) : filesToEmit, f => f !== configFile ? f.path : undefined); + host.reloadFS(filesToReload || files); + host.runQueuedTimeoutCallbacks(); + const project = projectService.configuredProjects.get(configFile.path); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles, + filesToEmit: mapDefined(filesToEmit, f => f !== libFile && f !== configFile ? f.path : undefined) + } + }]); + } + + function updateContentOfOpenFile(file: FileOrFolder, newContent: string) { + session.executeCommandSeq({ + command: server.CommandNames.Change, + arguments: { + file: file.path, + insertString: newContent, + endLine: 1, + endOffset: file.content.length, + line: 1, + offset: 1 + } + }); + file.content = newContent; + } + } + + it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { + const { moduleFile1, file1Consumer1, file1Consumer2, verifyProjectChangedEvent } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` + moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; + verifyProjectChangedEvent([moduleFile1]); + }); + + it("should be up-to-date with the reference map changes", () => { + const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState(); + + // Change file1Consumer1 content to `export let y = Foo();` + updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); + verifyProjectChangedEvent([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer2]); + + // Add the import statements back to file1Consumer1 + updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); + verifyProjectChangedEvent([file1Consumer1]); + + // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` + moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); + + // Multiple file edits in one go: + + // Change file1Consumer1 content to `export let y = Foo();` + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + }); + + it("should be up-to-date with deleted files", () => { + const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent } = getInitialState(); + + // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` + moduleFile1.content = `export var T: number;export function Foo() { };`; + + // Delete file1Consumer2 + const filesToLoad = filter(files, file => file !== file1Consumer2); + verifyProjectChangedEvent([moduleFile1, file1Consumer1], filesToLoad, [file1Consumer2]); + }); + + it("should be up-to-date with newly created files", () => { + const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent, } = getInitialState(); + + const file1Consumer3: FileOrFolder = { + path: "/a/b/file1Consumer3.ts", + content: `import {Foo} from "./moduleFile1"; let y = Foo();` + }; + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); + }); + + it("should detect changes in non-root files", () => { + const { moduleFile1, file1Consumer1, verifyProjectChangedEvent } = getInitialState({ + configObj: { files: [file1Consumer1Path] }, + firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path, libFile.path] + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1]); + + // change file1 internal, and verify only file1 is affected + moduleFile1.content += "var T1: number;"; + verifyProjectChangedEvent([moduleFile1]); + }); + + it("should return all files if a global file changed shape", () => { + const { globalFile3, verifyAffectedAllFiles } = getInitialState(); + + globalFile3.content += "var T2: string;"; + verifyAffectedAllFiles(); + }); + + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ + configObj: { compilerOptions: { isolatedModules: true } } + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1]); + }); + + it("should always return the file itself if '--out' or '--outFile' is specified", () => { + const outFilePath = "/a/b/out.js"; + const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ + configObj: { compilerOptions: { module: "system", outFile: outFilePath } } + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1]); + }); + + it("should return cascaded affected file list", () => { + const file1Consumer1Consumer1: FileOrFolder = { + path: "/a/b/file1Consumer1Consumer1.ts", + content: `import {y} from "./file1Consumer1";` + }; + const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); + + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); + verifyProjectChangedEvent([file1Consumer1, file1Consumer1Consumer1]); + + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + + // Change both files before the timeout + updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); + moduleFile1.content = `export var T2: number;export function Foo() { };`; + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); + }); + + it("should work fine for files with circular references", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: ` + /// + export var t1 = 10;` + }; + const file2: FileOrFolder = { + path: "/a/b/file2.ts", + content: ` + /// + export var t2 = 10;` + }; + const { configFile, verifyProjectChangedEvent, updateContentOfOpenFile } = getInitialState({ + getAdditionalFileOrFolder: () => [file1, file2], + firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] + }); + + updateContentOfOpenFile(file1, file1.content + "export var t3 = 10;"); + verifyProjectChangedEvent([file1, file2], [file1, file2, libFile, configFile]); + }); + + it("should detect removed code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { configFile, verifyProjectChangedEvent, moduleFile1 } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] + }); + + verifyProjectChangedEvent([referenceFile1], [libFile, referenceFile1, configFile], [moduleFile1]); + }); + + it("should detect non-existing code file", () => { + const referenceFile1: FileOrFolder = { + path: "/a/b/referenceFile1.ts", + content: ` + /// + export var x = Foo();` + }; + const { configFile, moduleFile2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] + }); + + updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); + verifyProjectChangedEvent([referenceFile1], [libFile, referenceFile1, configFile]); + + // Create module File2 and see both files are saved + verifyProjectChangedEvent([referenceFile1, moduleFile2], [libFile, moduleFile2, referenceFile1, configFile]); + }); + }); + } + + describe("when event handler is set in the session", () => { + verifyProjectChangedEvent(createSessionWithProjectChangedEventHandler); + + function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectChangeEventVerifier { + const projectChangedEvents: server.ProjectChangedEvent[] = []; + const session = createSession(host, { + eventHandler: e => { + if (e.eventName === server.ProjectChangedEvent) { + projectChangedEvents.push(e); + } + } + }); + + return { + session, + verifyProjectChangedEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) + }; + + function eventToString(event: server.ProjectChangedEvent) { + const eventToModify = event && { + eventName: event.eventName, + data: { + project: event.data.project.getProjectName(), + changedFiles: event.data.changedFiles, + filesToEmit: event.data.filesToEmit + } + }; + return JSON.stringify(eventToModify); + } + + function eventsToString(events: ReadonlyArray) { + return "[" + map(events, eventToString).join(",") + "]"; + } + + function verifyProjectChangedEventHandler(expectedEvents: ReadonlyArray) { + assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); + forEach(projectChangedEvents, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); + assert.strictEqual(actualEvent.data.project, expectedEvent.data.project); + verifyFiles("changedFiles", actualEvent.data.changedFiles, expectedEvent.data.changedFiles); + verifyFiles("filesToEmit", actualEvent.data.filesToEmit, expectedEvent.data.filesToEmit); + }); + + // Verified the events, reset them + projectChangedEvents.length = 0; + } + } + }); + + describe("when event handler is not set but session is created with canUseEvents = true", () => { + verifyProjectChangedEvent(createSessionThatUsesEvents); + + function createSessionThatUsesEvents(host: TestServerHost): ProjectChangeEventVerifier { + const session = createSession(host, { canUseEvents: true }); + + return { + session, + verifyProjectChangedEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) + }; + + function verifyProjectChangedEventHandler(expected: ReadonlyArray) { + const expectedEvents: protocol.ProjectChangedEventBody[] = map(expected, e => { + return { + projectName: e.data.project.getProjectName(), + changedFiles: e.data.changedFiles, + fileNamesToEmit: e.data.filesToEmit + }; + }); + const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; + const events: protocol.ProjectStructureChangedEvent[] = filter( + map( + host.getOutput(), s => convertToObject( + ts.parseJsonText("json.json", s.replace(outputEventRegex, "")), + [] + ) + ), + e => e.event === server.ProjectChangedEvent + ); + assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); + forEach(events, (actualEvent, i) => { + const expectedEvent = expectedEvents[i]; + assert.strictEqual(actualEvent.body.projectName, expectedEvent.projectName); + verifyFiles("changedFiles", actualEvent.body.changedFiles, expectedEvent.changedFiles); + verifyFiles("fileNamesToEmit", actualEvent.body.fileNamesToEmit, expectedEvent.fileNamesToEmit); + }); + + // Verified the events, reset them + host.clearOutput(); + } + } + }); + }); } From 4c79033894896fc57786aa1628e137329167e868 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 23 Aug 2017 13:34:41 -0700 Subject: [PATCH 076/144] Refactoring to watches and caching of system such that we minimize function expressions Also unified watcher info logging --- src/compiler/builder.ts | 4 +- src/compiler/core.ts | 13 +- src/compiler/program.ts | 15 +-- src/compiler/resolutionCache.ts | 43 +++---- src/compiler/sys.ts | 7 +- src/compiler/utilities.ts | 71 +++++++++-- src/compiler/watchedProgram.ts | 158 ++++++------------------- src/server/editorServices.ts | 203 ++++++++++++++------------------ src/server/lsHost.ts | 125 ++------------------ src/server/project.ts | 121 +++++++++---------- src/server/scriptInfo.ts | 2 +- 11 files changed, 301 insertions(+), 461 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 4fb90ca7143..68fb36b9144 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -120,7 +120,7 @@ namespace ts { // Remove existing file info onDeleteValue: removeExistingFileInfo, // We will update in place instead of deleting existing value and adding new one - onExistingValue: (_key, existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) + onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) } ); } @@ -137,7 +137,7 @@ namespace ts { return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined }; } - function removeExistingFileInfo(path: Path, existingFileInfo: FileInfo) { + function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) { registerChangedFile(path, existingFileInfo.fileName); emitHandler.removeScriptInfo(path); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 00e8065a9f9..bb4cf903a98 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2631,10 +2631,6 @@ namespace ts { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } - export interface HostForCaching extends PartialSystem { - useCaseSensitiveFileNames: boolean; - } - export interface CachedHost { addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; @@ -2649,11 +2645,15 @@ namespace ts { readonly directories: string[]; } - export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { + export function createCachedPartialSystem(host: PartialSystem): CachedPartialSystem { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, + newLine: host.newLine, + readFile: (path, encoding) => host.readFile(path, encoding), + write: s => host.write(s), writeFile, fileExists, directoryExists, @@ -2663,7 +2663,8 @@ namespace ts { readDirectory, addOrDeleteFileOrFolder, addOrDeleteFile, - clearCache + clearCache, + exit: code => host.exit(code) }; function toPath(fileName: string) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c3b83e1ba7c..22459d36cb0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -461,7 +461,6 @@ namespace ts { program: Program, missingFileWatches: Map, createMissingFileWatch: (missingFilePath: Path) => FileWatcher, - closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void ) { const missingFilePaths = program.getMissingFilePaths(); const newMissingFilePathMap = arrayToSet(missingFilePaths); @@ -474,7 +473,7 @@ namespace ts { createNewValue: createMissingFileWatch, // Files that are no longer missing (e.g. because they are no longer required) // should no longer be watched. - onDeleteValue: closeExistingMissingFilePathFileWatcher + onDeleteValue: closeFileWatcher } ); } @@ -493,8 +492,7 @@ namespace ts { export function updateWatchingWildcardDirectories( existingWatchedForWildcards: Map, wildcardDirectories: Map, - watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher, - closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flagsChanged: boolean) => void + watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher ) { mutateMap( existingWatchedForWildcards, @@ -503,8 +501,7 @@ namespace ts { // Create new watch and recursive info createNewValue: createWildcardDirectoryWatcher, // Close existing watch thats not needed any more - onDeleteValue: (directory, wildcardDirectoryWatcher) => - closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ false), + onDeleteValue: closeFileWatcherOf, // Close existing watch that doesnt match in the flags onExistingValue: updateWildcardDirectoryWatcher } @@ -518,13 +515,13 @@ namespace ts { }; } - function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags) { + function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { // Watcher needs to be updated if the recursive flags dont match - if (wildcardDirectoryWatcher.flags === flags) { + if (existingWatcher.flags === flags) { return; } - closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true); + existingWatcher.watcher.close(); existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); } } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6d7c1f6c0e4..8001dd81bd9 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -27,14 +27,14 @@ namespace ts { } interface FailedLookupLocationsWatcher { - fileWatcher: FileWatcher; + watcher: FileWatcher; refCount: number; } export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher, + watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher, log: (s: string) => void, projectName?: string, getGlobalCache?: () => string | undefined): ResolutionCache { @@ -69,10 +69,7 @@ namespace ts { function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache - clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => { - log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`); - failedLookupLocationWatcher.fileWatcher.close(); - }); + clearMap(failedLookupLocationsWatches, closeFileWatcherOf); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -142,7 +139,7 @@ namespace ts { } else { resolution = loader(name, containingFile, compilerOptions, host); - updateFailedLookupLocationWatches(containingFile, name, existingResolution && existingResolution.failedLookupLocations, resolution.failedLookupLocations); + updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { @@ -205,46 +202,44 @@ namespace ts { m => m.resolvedModule, r => r.resolvedFileName, logChanges); } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); if (failedLookupLocationWatcher) { failedLookupLocationWatcher.refCount++; - log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`); + log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`); } else { - log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name); - failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 }); + const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 }); } } - function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); Debug.assert(!!failedLookupLocationWatcher); failedLookupLocationWatcher.refCount--; if (failedLookupLocationWatcher.refCount) { - log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}: refCount: ${failedLookupLocationWatcher.refCount}`); + log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`); } else { - log(`Watcher: FailedLookupLocations: Status: Closing the file watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - failedLookupLocationWatcher.fileWatcher.close(); + failedLookupLocationWatcher.watcher.close(); failedLookupLocationsWatches.delete(failedLookupLocationPath); } } - type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void; - function withFailedLookupLocations(failedLookupLocations: ReadonlyArray, containingFile: string, name: string, fn: FailedLookupLocationAction) { + type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void; + function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction) { forEach(failedLookupLocations, failedLookupLocation => { - fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name); + fn(failedLookupLocation, toPath(failedLookupLocation)); }); } - function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray | undefined, failedLookupLocations: ReadonlyArray) { + function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation); + withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation); // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher); + withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher); } function invalidateResolutionCacheOfDeletedFile( @@ -255,8 +250,8 @@ namespace ts { cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); - value.forEach((resolution, name) => { - withFailedLookupLocations(resolution.failedLookupLocations, path, name, closeFailedLookupLocationWatcher); + value.forEach(resolution => { + withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher); }); } else if (value) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 982d6793819..7a419be7c2d 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -34,6 +34,10 @@ namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ export interface PartialSystem { + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; directoryExists(path: string): boolean; @@ -41,6 +45,7 @@ namespace ts { getCurrentDirectory(): string; getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + exit(exitCode?: number): void; } export interface System extends PartialSystem { @@ -48,7 +53,6 @@ namespace ts { newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; - readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that @@ -65,7 +69,6 @@ namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; - exit(exitCode?: number): void; realpath?(path: string): string; /*@internal*/ getEnvironmentVariable(name: string): string; /*@internal*/ tryEnableSourceMapsForHost?(): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f09ce446491..7e43d793f0b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3490,17 +3490,15 @@ namespace ts { /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ - export function clearMap(map: Map, onDeleteValue: (key: string, existingValue: T) => void) { + export function clearMap(map: Map, onDeleteValue: (existingValue: T, key: string) => void) { // Remove all - map.forEach((existingValue, key) => { - onDeleteValue(key, existingValue); - }); + map.forEach(onDeleteValue); map.clear(); } export interface MutateMapOptions { createNewValue(key: string, valueInNewMap: U): T; - onDeleteValue(key: string, existingValue: T): void; + onDeleteValue(existingValue: T, key: string): void; /** * If present this is called with the key when there is value for that key both in new map as well as existing map provided @@ -3508,7 +3506,7 @@ namespace ts { * If the key is removed, caller will get callback of createNewValue for that key. * If this callback is not provided, the value of such keys is not updated. */ - onExistingValue?(key: string, existingValue: T, valueInNewMap: U): void; + onExistingValue?(existingValue: T, valueInNewMap: U, key: string): void; } /** @@ -3524,11 +3522,11 @@ namespace ts { // Not present any more in new map, remove it if (valueInNewMap === undefined) { map.delete(key); - onDeleteValue(key, existingValue); + onDeleteValue(existingValue, key); } // If present notify about existing values else if (onExistingValue) { - onExistingValue(key, existingValue, valueInNewMap); + onExistingValue(existingValue, valueInNewMap, key); } }); @@ -3544,6 +3542,63 @@ namespace ts { clearMap(map, options.onDeleteValue); } } + + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { + return host.watchFile(file, cb); + } + + export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb); + } + + export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; + export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { + return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); + } + + export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path); + } + + export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { + const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; + return host.watchDirectory(directory, fileName => { + cb(getNormalizedAbsolutePath(fileName, directory)); + }, recursive); + } + + export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; + return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags); + } + + type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; + type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { + const info = `PathInfo: ${file}`; + log(`${watcherCaption}Added: ${info}`); + const watcher = addWatch(host, file, (fileName, cbOptional1?) => { + const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : ""; + log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`); + cb(fileName, cbOptional1, optional); + }, optional); + return { + close: () => { + log(`${watcherCaption}Close: ${info}`); + watcher.close(); + } + }; + } + + export function closeFileWatcher(watcher: FileWatcher) { + watcher.close(); + } + + export function closeFileWatcherOf(objWithWatcher: T) { + objWithWatcher.watcher.close(); + } } namespace ts { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 084c707cea8..2a23e66763b 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -4,8 +4,7 @@ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - export type DiagnosticWorker = (diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System) => void; - export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; + export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; export interface WatchingSystemHost { // FS system to use system: System; @@ -19,7 +18,7 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: System, program: Program, builder: Builder): void; + afterCompile(host: PartialSystem, program: Program, builder: Builder): void; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -62,7 +61,7 @@ namespace ts { system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); } - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { let configFileText: string; try { configFileText = system.readFile(configFileName); @@ -90,7 +89,7 @@ namespace ts { return configParseResult; } - function reportEmittedFiles(files: string[], system: System): void { + function reportEmittedFiles(files: string[], system: PartialSystem): void { if (!files || files.length === 0) { return; } @@ -101,7 +100,7 @@ namespace ts { } } - export function handleEmitOutputAndReportErrors(system: System, program: Program, + export function handleEmitOutputAndReportErrors(system: PartialSystem, program: Program, emittedFiles: string[], emitSkipped: boolean, diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter ): ExitStatus { @@ -142,7 +141,7 @@ namespace ts { afterCompile: compileWatchedProgram, }; - function compileWatchedProgram(host: System, program: Program, builder: Builder) { + function compileWatchedProgram(host: PartialSystem, program: Program, builder: Builder) { // First get and report any syntactic errors. let diagnostics = program.getSyntacticDiagnostics(); let reportSemanticDiagnostics = false; @@ -249,26 +248,28 @@ namespace ts { let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations + const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop; + const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher; + const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; + const watchDirectory = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; + watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - let host: System; + const host = configFileName ? createCachedPartialSystem(system) : system; if (configFileName) { - host = createCachedSystem(system); - configFileWatcher = system.watchFile(configFileName, onConfigFileChanged); - } - else { - host = system; + configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog); } const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); // Cache for the module resolution const resolutionCache = createResolutionCache( fileName => toPath(fileName), () => compilerOptions, watchFailedLookupLocation, - s => writeLog(s) + writeLog ); // There is no extra check needed since we can just rely on the program to decide emit @@ -303,7 +304,7 @@ namespace ts { builder.onProgramUpdateGraph(program, hasInvalidatedResolution); // Update watches - updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath, closeMissingFilePathWatcher); + updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); if (missingFilePathsRequestedForRelease) { // These are the paths that program creater told us as not in use any more but were missing on the disk. // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, @@ -324,7 +325,7 @@ namespace ts { function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { const newLine = getNewLineCharacter(options, system); - const realpath = host.realpath && ((path: string) => host.realpath(path)); + const realpath = system.realpath && ((path: string) => system.realpath(path)); return { getSourceFile: getVersionedSourceFile, @@ -333,14 +334,14 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile: fileName => host.readFile(fileName), - trace: (s: string) => host.write(s + newLine), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", getDirectories: (path: string) => host.getDirectories(path), realpath, resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), @@ -365,7 +366,7 @@ namespace ts { } function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(host.getExecutingFilePath())); + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { @@ -390,7 +391,7 @@ namespace ts { hostSourceFile.sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchSourceFileForChanges(path); + hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); } } else { @@ -403,7 +404,7 @@ namespace ts { let fileWatcher: FileWatcher; if (sourceFile) { sourceFile.version = "0"; - fileWatcher = watchSourceFileForChanges(path); + fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); } else { @@ -418,7 +419,7 @@ namespace ts { let text: string; try { performance.mark("beforeIORead"); - text = host.readFile(fileName, compilerOptions.charset); + text = system.readFile(fileName, compilerOptions.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -502,7 +503,7 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); needsReload = false; - const cachedHost = host as CachedSystem; + const cachedHost = host as CachedPartialSystem; cachedHost.clearCache(); const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; @@ -517,13 +518,7 @@ namespace ts { watchConfigFileWildCardDirectories(); } - function watchSourceFileForChanges(path: Path) { - return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); - } - - function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - + function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { updateCachedSystemWithFile(fileName, path, eventKind); const hostSourceFile = sourceFilesCache.get(path); if (hostSourceFile) { @@ -553,35 +548,29 @@ namespace ts { function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - (host as CachedSystem).addOrDeleteFile(fileName, path, eventKind); + (host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); } } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { - return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, failedLookupLocationPath, containingFile, name)); + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { + return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog); } - function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { - writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); + function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); scheduleProgramUpdate(); } function watchMissingFilePath(missingFilePath: Path) { - return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); + return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } - function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { - fileWatcher.close(); - } - - function onMissingFileChange(fileName: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { - writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { updateCachedSystemWithFile(fileName, missingFilePath, eventKind); if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { - closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); + missingFilesMap.get(missingFilePath).close(); missingFilesMap.delete(missingFilePath); // Delete the entry in the source files cache so that new source file is created @@ -596,19 +585,12 @@ namespace ts { updateWatchingWildcardDirectories( watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), createMapFromTemplate(configFileWildCardDirectories), - watchWildCardDirectory, - stopWatchingWildCardDirectory + watchWildCardDirectory ); } function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) { - return host.watchDirectory(directory, fileOrFolder => - onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileOrFolder, directory)), - (flags & WatchDirectoryFlags.Recursive) !== 0); - } - - function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatcher, _recursiveChanged: boolean) { - watcher.close(); + return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog); } function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) { @@ -617,7 +599,7 @@ namespace ts { const fileOrFolderPath = toPath(fileOrFolder); // Since the file existance changed, update the sourceFiles cache - (host as CachedSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); removeSourceFile(fileOrFolderPath); // If a change was made inside "folder/file", node will trigger the callback twice: @@ -628,8 +610,6 @@ namespace ts { return; } - writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileOrFolder}`); - // Reload is pending, do the reload if (!needsReload) { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); @@ -643,70 +623,8 @@ namespace ts { } } - function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { - writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - scheduleProgramReload(); - } - - function writeLog(s: string) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - if (hasDiagnostics) { - host.write(s); - } - } - function computeHash(data: string) { return system.createHash ? system.createHash(data) : data; } } - - interface CachedSystem extends System, CachedHost { - } - - function createCachedSystem(host: System): CachedSystem { - const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; - const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; - const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; - const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; - const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; - const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; - const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; - const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; - const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; - const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; - - const cachedPartialSystem = createCachedPartialSystem(host); - return { - args: host.args, - newLine: host.newLine, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - write: s => host.write(s), - readFile: (path, encoding?) => host.readFile(path, encoding), - getFileSize, - writeFile: (fileName, data, writeByteOrderMark?) => cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark), - watchFile, - watchDirectory, - resolvePath: path => host.resolvePath(path), - fileExists: fileName => cachedPartialSystem.fileExists(fileName), - directoryExists: dir => cachedPartialSystem.directoryExists(dir), - createDirectory: dir => cachedPartialSystem.createDirectory(dir), - getExecutingFilePath: () => host.getExecutingFilePath(), - getCurrentDirectory: () => cachedPartialSystem.getCurrentDirectory(), - getDirectories: dir => cachedPartialSystem.getDirectories(dir), - readDirectory: (path, extensions, excludes, includes, depth) => cachedPartialSystem.readDirectory(path, extensions, excludes, includes, depth), - getModifiedTime, - createHash, - getMemoryUsage, - exit: exitCode => host.exit(exitCode), - realpath, - getEnvironmentVariable: name => host.getEnvironmentVariable(name), - tryEnableSourceMapsForHost, - debugMode: host.debugMode, - setTimeout, - clearTimeout, - addOrDeleteFileOrFolder: (fileOrFolder, fileOrFolderPath) => cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath), - addOrDeleteFile: (file, filePath, eventKind) => cachedPartialSystem.addOrDeleteFile(file, filePath, eventKind), - clearCache: () => cachedPartialSystem.clearCache() - }; - } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 385ade0b816..292f6e3b90a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -248,22 +248,7 @@ namespace ts.server { TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", ConfigFileForInferredRoot = "Config file for the inferred project root", - FailedLookupLocation = "Failed lookup locations in module resolution" - } - - /* @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", - ConfigProjectCreated = "Config file project created", - FileClosed = "File is closed" + FailedLookupLocation = "Directory of Failed lookup locations in module resolution" } const enum ConfigFileWatcherStatus { @@ -276,9 +261,6 @@ namespace ts.server { RootOfInferredProjectFalse = "Open file was set as not inferred root", } - /* @internal */ - export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; - interface ConfigFileExistenceInfo { /** * Cached value of existence of config file @@ -318,6 +300,10 @@ namespace ts.server { allowLocalPluginLoads?: boolean; } + type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher; + type WatchFilePath = (host: ServerHost, file: string, cb: FilePathWatcherCallback, path: Path, watchType: WatchType, project?: Project) => FileWatcher; + type WatchDirectory = (host: ServerHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, watchType: WatchType, project?: Project) => FileWatcher; + export class ProjectService { public readonly typingsCache: TypingsCache; @@ -392,6 +378,13 @@ namespace ts.server { /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects = createMap(); + /*@internal*/ + readonly watchFile: WatchFile; + /*@internal*/ + readonly watchFilePath: WatchFilePath; + /*@internal*/ + readonly watchDirectory: WatchDirectory; + constructor(opts: ProjectServiceOptions) { this.host = opts.host; this.logger = opts.logger; @@ -422,6 +415,21 @@ namespace ts.server { }; this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory); + if (this.logger.hasLevel(LogLevel.verbose)) { + this.watchFile = (host, file, cb, watchType, project) => ts.addFileWatcherWithLogging(host, file, cb, this.createWatcherLog(watchType, project)); + this.watchFilePath = (host, file, cb, path, watchType, project) => ts.addFilePathWatcherWithLogging(host, file, cb, path, this.createWatcherLog(watchType, project)); + this.watchDirectory = (host, dir, cb, flags, watchType, project) => ts.addDirectoryWatcherWithLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project)); + } + else { + this.watchFile = ts.addFileWatcher; + this.watchFilePath = ts.addFilePathWatcher; + this.watchDirectory = ts.addDirectoryWatcher; + } + } + + private createWatcherLog(watchType: WatchType, project: Project | undefined): (s: string) => void { + const detailedInfo = ` Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`; + return s => this.logger.info(s + detailedInfo); } toPath(fileName: string, basePath = this.currentDirectory) { @@ -674,7 +682,7 @@ namespace ts.server { else if (!info.isScriptOpen()) { if (info.containingProjects.length === 0) { // Orphan script info, remove it as we can always reload it on next open file request - this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange); + this.stopWatchingScriptInfo(info); this.filenameToScriptInfo.delete(info.path); } else { @@ -687,7 +695,7 @@ namespace ts.server { } private handleDeletedFile(info: ScriptInfo) { - this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted); + this.stopWatchingScriptInfo(info); // TODO: handle isOpen = true case @@ -705,8 +713,8 @@ namespace ts.server { } /* @internal */ - onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: string) { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); project.updateTypes(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -717,8 +725,8 @@ namespace ts.server { * @param fileName the absolute file name that changed in watched directory */ /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: string) { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); const configFilename = project.getConfigFilePath(); // If a change was made inside "folder/file", node will trigger the callback twice: @@ -730,7 +738,7 @@ namespace ts.server { } const configFileSpecs = project.configFileSpecs; - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); @@ -923,7 +931,7 @@ namespace ts.server { this.filenameToScriptInfo.forEach(info => { if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it - this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo); + this.stopWatchingScriptInfo(info); this.filenameToScriptInfo.delete(info.path); } }); @@ -967,10 +975,7 @@ namespace ts.server { // close existing watcher if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) { const configFileName = project.getConfigFilePath(); - this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFileExistenceInfo.configFileWatcherForRootOfInferredProject, WatcherCloseReason.ConfigProjectCreated - ); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close(); configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; this.logConfigFileWatchUpdate(configFileName, project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } @@ -1002,11 +1007,7 @@ namespace ts.server { // created when any of the script infos are added as root of inferred project if (this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject); - configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatchUpdate(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.createConfigFileWatcherOfConfigFileExistence(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo); } } else { @@ -1016,7 +1017,7 @@ namespace ts.server { } private logConfigFileWatchUpdate(configFileName: NormalizedPath, canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo, status: ConfigFileWatcherStatus) { - if (!this.logger.loggingEnabled()) { + if (!this.logger.hasLevel(LogLevel.verbose)) { return; } const inferredRoots: string[] = []; @@ -1036,21 +1037,32 @@ namespace ts.server { this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } + /** + * Create the watcher for the configFileExistenceInfo + */ + private createConfigFileWatcherOfConfigFileExistence( + configFileName: NormalizedPath, + canonicalConfigFilePath: string, + configFileExistenceInfo: ConfigFileExistenceInfo + ) { + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFile( + this.host, + configFileName, + (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind), + WatchType.ConfigFileForInferredRoot + ); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + /** * Close the config file watcher in the cached ConfigFileExistenceInfo * if there arent any open files that are root of inferred project */ - private closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo, - reason: WatcherCloseReason - ) { + private closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo: ConfigFileExistenceInfo) { // Close the config file watcher if there are no more open files that are root of inferred project if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject && !this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { - this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFileExistenceInfo.configFileWatcherForRootOfInferredProject, reason - ); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close(); configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; } } @@ -1074,9 +1086,7 @@ namespace ts.server { if (infoIsRootOfInferredProject) { // But if it is a root, it could be the last script info that is root of inferred project // and hence we would need to close the config file watcher - this.closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName, configFileExistenceInfo, WatcherCloseReason.FileClosed - ); + this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo); } // If there are no open files that are impacted by configFileExistenceInfo after closing this script info @@ -1097,27 +1107,24 @@ namespace ts.server { startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { - let configFilePresenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); - if (!configFilePresenceInfo) { + let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); + if (!configFileExistenceInfo) { // Create the cache - configFilePresenceInfo = { + configFileExistenceInfo = { exists: this.host.fileExists(configFileName), openFilesImpactedByConfigFile: createMap() }; - this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFilePresenceInfo); + this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo); } // Set this file as the root of inferred project - configFilePresenceInfo.openFilesImpactedByConfigFile.set(info.path, true); - this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue); + configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, true); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue); // If there is no configured project for this config file, add the file watcher - if (!configFilePresenceInfo.configFileWatcherForRootOfInferredProject && + if (!configFileExistenceInfo.configFileWatcherForRootOfInferredProject && !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { - configFilePresenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.createConfigFileWatcherOfConfigFileExistence(configFileName, canonicalConfigFilePath, configFileExistenceInfo); } }); } @@ -1126,7 +1133,7 @@ namespace ts.server { * This is called by inferred project whenever root script info is removed from it */ /* @internal */ - stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { + stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { @@ -1137,9 +1144,7 @@ namespace ts.server { this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectFalse); // Close the config file watcher - this.closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName, configFileExistenceInfo, reason - ); + this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo); } }); } @@ -1248,7 +1253,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: PartialSystem) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1385,8 +1390,8 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) { - const cachedServerHost = new CachedServerHost(this.host); - const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); + const cachedPartialSystem = createCachedPartialSystem(this.host); + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedPartialSystem); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( @@ -1397,12 +1402,16 @@ namespace ts.server { projectOptions.compilerOptions, languageServiceEnabled, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, - cachedServerHost); + cachedPartialSystem); project.configFileSpecs = configFileSpecs; // TODO: We probably should also watch the configFiles that are extended - project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, - configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind) + project.configFileWatcher = this.watchFile( + this.host, + configFileName, + (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind), + WatchType.ConfigFilePath, + project ); if (languageServiceEnabled) { project.watchWildcards(projectOptions.wildcardDirectories); @@ -1490,7 +1499,7 @@ namespace ts.server { /* @internal */ reloadConfiguredProject(project: ConfiguredProject) { // At this point, there is no reason to not have configFile in the host - const host = project.getCachedServerHost(); + const host = project.getCachedPartialSystem(); // Clear the cache since we are reloading the project from disk host.clearCache(); @@ -1505,8 +1514,8 @@ namespace ts.server { project.setProjectErrors(configFileErrors); if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { project.disableLanguageService(); - project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize); - project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize); + project.stopWatchingWildCards(); + project.stopWatchingTypeRoots(); } else { project.enableLanguageService(); @@ -1594,7 +1603,7 @@ namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ /*@internal*/ - getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) { + getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: PartialSystem) { return this.getOrCreateScriptInfoForNormalizedPath( toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn @@ -1610,20 +1619,23 @@ namespace ts.server { // do not watch files with mixed content - server doesn't know how to interpret it if (!info.hasMixedContent) { const { fileName } = info; - info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName, - (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind) + info.fileWatcher = this.watchFile( + this.host, + fileName, + (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind), + WatchType.ClosedScriptInfo ); } } - private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) { + private stopWatchingScriptInfo(info: ScriptInfo) { if (info.fileWatcher) { - this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason); + info.fileWatcher.close(); info.fileWatcher = undefined; } } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { @@ -1645,7 +1657,7 @@ namespace ts.server { } if (info) { if (openedByClient && !info.isScriptOpen()) { - this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened); + this.stopWatchingScriptInfo(info); info.open(fileContent); if (hasMixedContent) { info.registerFileUpdate(); @@ -1693,39 +1705,6 @@ 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, flags: WatchDirectoryFlags, reason: WatcherCloseReason) { - const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - 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, flags: WatchDirectoryFlags) { - const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - 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(); } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 44b9893bc3d..b758cd796bf 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -4,110 +4,6 @@ /// namespace ts.server { - /*@internal*/ - export class CachedServerHost implements ServerHost { - args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - - private readonly cachedPartialSystem: CachedPartialSystem; - - readonly trace: (s: string) => void; - readonly realpath?: (path: string) => string; - - constructor(private readonly host: ServerHost) { - this.args = host.args; - this.newLine = host.newLine; - this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; - if (host.trace) { - this.trace = s => host.trace(s); - } - if (this.host.realpath) { - this.realpath = path => this.host.realpath(path); - } - this.cachedPartialSystem = createCachedPartialSystem(host); - } - - write(s: string) { - return this.host.write(s); - } - - writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - this.cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark); - } - - resolvePath(path: string) { - return this.host.resolvePath(path); - } - - createDirectory(path: string) { - Debug.fail(`Why is createDirectory called on the cached server for ${path}`); - } - - getExecutingFilePath() { - return this.host.getExecutingFilePath(); - } - - getCurrentDirectory() { - return this.cachedPartialSystem.getCurrentDirectory(); - } - - exit(exitCode?: number) { - Debug.fail(`Why is exit called on the cached server: ${exitCode}`); - } - - getEnvironmentVariable(name: string) { - Debug.fail(`Why is getEnvironmentVariable called on the cached server: ${name}`); - return this.host.getEnvironmentVariable(name); - } - - getDirectories(rootDir: string) { - return this.cachedPartialSystem.getDirectories(rootDir); - } - - readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return this.cachedPartialSystem.readDirectory(rootDir, extensions, excludes, includes, depth); - } - - fileExists(fileName: string): boolean { - return this.cachedPartialSystem.fileExists(fileName); - } - - directoryExists(dirPath: string) { - return this.cachedPartialSystem.directoryExists(dirPath); - } - - readFile(path: string, encoding?: string): string { - return this.host.readFile(path, encoding); - } - - addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath, fileOrFolderPath: Path) { - return this.cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - addOrDeleteFile(file: string, path: Path, eventKind: FileWatcherEventKind) { - return this.cachedPartialSystem.addOrDeleteFile(file, path, eventKind); - } - - clearCache() { - return this.cachedPartialSystem.clearCache(); - } - - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { - return this.host.setTimeout(callback, ms, ...args); - } - clearTimeout(timeoutId: any) { - return this.host.clearTimeout(timeoutId); - } - setImmediate(callback: (...args: any[]) => void, ...args: any[]) { - this.host.setImmediate(callback, ...args); - } - clearImmediate(timeoutId: any) { - this.host.clearImmediate(timeoutId); - } - - } - export class LSHost implements LanguageServiceHost, ModuleResolutionHost { /*@internal*/ compilationSettings: CompilerOptions; @@ -124,21 +20,26 @@ namespace ts.server { * file system entries as we would anyways be watching files in the project (so safe to cache) */ /*@internal*/ - host: ServerHost; + host: PartialSystem; - constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { + constructor(host: PartialSystem, private project: Project, private readonly cancellationToken: HostCancellationToken) { this.host = host; this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - if (host.trace) { - this.trace = s => host.trace(s); + const serverHost = this.getServerHost(); + if (serverHost.trace) { + this.trace = s => serverHost.trace(s); } - if (this.host.realpath) { - this.realpath = path => this.host.realpath(path); + if (serverHost.realpath) { + this.realpath = path => serverHost.realpath(path); } } + private getServerHost() { + return this.project.projectService.host; + } + dispose() { this.project = undefined; this.host = undefined; @@ -173,7 +74,7 @@ namespace ts.server { } getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath())); + const nodeModuleBinDir = getDirectoryPath(normalizePath(this.getServerHost().getExecutingFilePath())); return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); } @@ -207,7 +108,7 @@ namespace ts.server { } resolvePath(path: string): string { - return this.host.resolvePath(path); + return this.getServerHost().resolvePath(path); } fileExists(file: string): boolean { diff --git a/src/server/project.ts b/src/server/project.ts index cce0fc5b510..ec58e4cb88d 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -198,7 +198,7 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - host: ServerHost) { + host: PartialSystem) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -216,7 +216,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - (failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name), + (failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath), s => this.projectService.logger.info(s), this.getProjectName(), () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined @@ -233,17 +233,24 @@ namespace ts.server { this.markAsDirty(); } - private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { + return this.projectService.watchFile( + this.projectService.host, + failedLookupLocation, + (fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath), + WatchType.FailedLookupLocation, + this + ); + } + + private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { // There is some kind of change in the failed lookup location, update the program - return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (fileName, eventKind) => { - this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); - } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); - }); + if (this.projectKind === ProjectKind.Configured) { + (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); + } + this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } private setInternalCompilerOptionsForEmittingJsFiles() { @@ -387,9 +394,7 @@ namespace ts.server { // Clean up file watchers waiting for missing files if (this.missingFilesMap) { - clearMap(this.missingFilesMap, (missingFilePath, fileWatcher) => { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose); - }); + clearMap(this.missingFilesMap, closeFileWatcher); this.missingFilesMap = undefined; } @@ -698,10 +703,7 @@ namespace ts.server { this.program, this.missingFilesMap || (this.missingFilesMap = createMap()), // Watch the missing files - missingFilePath => this.addMissingFileWatcher(missingFilePath), - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - (missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded) + missingFilePath => this.addMissingFileWatcher(missingFilePath) ); } @@ -726,30 +728,29 @@ namespace ts.server { } private addMissingFileWatcher(missingFilePath: Path) { - const fileWatcher = this.projectService.addFileWatcher( - WatchType.MissingFilePath, this, missingFilePath, + const fileWatcher = this.projectService.watchFile( + this.projectService.host, + missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { this.missingFilesMap.delete(missingFilePath); - this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); + fileWatcher.close(); // When a missing file is created, we should update the graph. this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - } + }, + WatchType.MissingFilePath, + this ); return fileWatcher; } - private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason); - } - isWatchedMissingFile(path: Path) { return this.missingFilesMap && this.missingFilesMap.has(path); } @@ -942,7 +943,7 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { - this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded); + this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) { @@ -968,7 +969,7 @@ namespace ts.server { } close() { - forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.ProjectClose)); + forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info)); super.close(); } @@ -1014,8 +1015,8 @@ namespace ts.server { compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, - cachedServerHost: CachedServerHost) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedServerHost); + cachedPartialSystem: PartialSystem) { + super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedPartialSystem); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); } @@ -1034,8 +1035,8 @@ namespace ts.server { } /*@internal*/ - getCachedServerHost() { - return this.lsHost.host as CachedServerHost; + getCachedPartialSystem() { + return this.lsHost.host as CachedPartialSystem; } getConfigFilePath() { @@ -1167,29 +1168,21 @@ namespace ts.server { this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), wildcardDirectories, // Create new directory watcher - (directory, flags) => this.projectService.addDirectoryWatcher( - WatchType.WildcardDirectories, this, directory, + (directory, flags) => this.projectService.watchDirectory( + this.projectService.host, + directory, path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - flags - ), - // Close directory watcher - (directory, wildcardDirectoryWatcher, flagsChanged) => this.closeWildcardDirectoryWatcher( - directory, wildcardDirectoryWatcher, flagsChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded + flags, + WatchType.WildcardDirectories, + this ) ); } - private closeWildcardDirectoryWatcher(directory: string, { watcher, flags }: WildcardDirectoryWatcher, closeReason: WatcherCloseReason) { - this.projectService.closeDirectoryWatcher(WatchType.WildcardDirectories, this, directory, watcher, flags, closeReason); - } - /*@internal*/ - stopWatchingWildCards(reason: WatcherCloseReason) { + stopWatchingWildCards() { if (this.directoriesWatchedForWildcards) { - clearMap( - this.directoriesWatchedForWildcards, - (directory, wildcardDirectoryWatcher) => this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, reason) - ); + clearMap(this.directoriesWatchedForWildcards, closeFileWatcherOf); this.directoriesWatchedForWildcards = undefined; } } @@ -1202,26 +1195,24 @@ namespace ts.server { newTypeRoots, { // Create new watch - createNewValue: root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root, - path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None + createNewValue: root => this.projectService.watchDirectory( + this.projectService.host, + root, + path => this.projectService.onTypeRootFileChanged(this, path), + WatchDirectoryFlags.None, + WatchType.TypeRoot, + this ), // Close existing watch thats not needed any more - onDeleteValue: (directory, watcher) => this.projectService.closeDirectoryWatcher( - WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded - ) + onDeleteValue: closeFileWatcher } ); } /*@internal*/ - stopWatchingTypeRoots(reason: WatcherCloseReason) { + stopWatchingTypeRoots() { if (this.typeRootsWatchers) { - clearMap( - this.typeRootsWatchers, - (directory, watcher) => - this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this, - directory, watcher, WatchDirectoryFlags.None, reason) - ); + clearMap(this.typeRootsWatchers, closeFileWatcher); this.typeRootsWatchers = undefined; } } @@ -1230,12 +1221,12 @@ namespace ts.server { super.close(); if (this.configFileWatcher) { - this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); + this.configFileWatcher.close(); this.configFileWatcher = undefined; } - this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); - this.stopWatchingWildCards(WatcherCloseReason.ProjectClose); + this.stopWatchingTypeRoots(); + this.stopWatchingWildCards(); } addOpenRef() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 93781df95c0..c35f26e3d3c 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -237,7 +237,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.lsHost.host as CachedServerHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + (p.lsHost.host as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways From 5aafd3f06c2cd8919d0fa4b168bd6f695666a1df Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 23 Aug 2017 13:34:41 -0700 Subject: [PATCH 077/144] Reduce number of watches for failed lookup locations as part of module resolution --- src/compiler/resolutionCache.ts | 78 ++++++++++++++++++++------------- src/compiler/utilities.ts | 12 +++++ src/compiler/watchedProgram.ts | 22 +++++++--- src/server/editorServices.ts | 4 +- src/server/project.ts | 26 ++++++----- 5 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 8001dd81bd9..dd03a055804 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path): void; + onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean; createHasInvalidatedResolution(): HasInvalidatedResolution; @@ -26,15 +26,17 @@ namespace ts { isInvalidated?: boolean; } - interface FailedLookupLocationsWatcher { + interface DirectoryWatchesOfFailedLookup { + /** watcher for the directory of failed lookup */ watcher: FileWatcher; - refCount: number; + /** map with key being the failed lookup location path and value being the actual location */ + mapLocations: MultiMap; } export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher, + watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher, log: (s: string) => void, projectName?: string, getGlobalCache?: () => string | undefined): ResolutionCache { @@ -49,7 +51,7 @@ namespace ts { const resolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); - const failedLookupLocationsWatches = createMap(); + const directoryWatchesOfFailedLookups = createMap(); return { setModuleResolutionHost, @@ -58,7 +60,7 @@ namespace ts { resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, - invalidateResolutionOfChangedFailedLookupLocation, + onFileAddOrRemoveInDirectoryOfFailedLookup, createHasInvalidatedResolution, clear }; @@ -69,7 +71,7 @@ namespace ts { function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache - clearMap(failedLookupLocationsWatches, closeFileWatcherOf); + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -203,43 +205,51 @@ namespace ts { } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); - if (failedLookupLocationWatcher) { - failedLookupLocationWatcher.refCount++; - log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`); + const dirPath = getDirectoryPath(failedLookupLocationPath); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + if (watches) { + watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation); } else { - const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 }); + const mapLocations = createMultiMap(); + mapLocations.add(failedLookupLocationPath, failedLookupLocation); + directoryWatchesOfFailedLookups.set(dirPath, { + watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)), + mapLocations + }); } } function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); - Debug.assert(!!failedLookupLocationWatcher); - failedLookupLocationWatcher.refCount--; - if (failedLookupLocationWatcher.refCount) { - log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`); - } - else { - failedLookupLocationWatcher.watcher.close(); - failedLookupLocationsWatches.delete(failedLookupLocationPath); + const dirPath = getDirectoryPath(failedLookupLocationPath); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation); + if (watches.mapLocations.size === 0) { + watches.watcher.close(); + directoryWatchesOfFailedLookups.delete(dirPath); } } type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void; - function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction) { - forEach(failedLookupLocations, failedLookupLocation => { - fn(failedLookupLocation, toPath(failedLookupLocation)); - }); + function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction, startIndex?: number) { + if (failedLookupLocations) { + for (let i = startIndex || 0; i < failedLookupLocations.length; i++) { + fn(failedLookupLocations[i], toPath(failedLookupLocations[i])); + } + } } function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { + log(`Resolution cache: Updating...`); + const index = existingFailedLookupLocations && failedLookupLocations ? + findDiffIndex(failedLookupLocations, existingFailedLookupLocations) : + 0; + // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation); + withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation, index); // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher); + withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher, index); } function invalidateResolutionCacheOfDeletedFile( @@ -291,9 +301,15 @@ namespace ts { invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } - function invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath: Path) { - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(failedLookupLocationPath, resolvedTypeReferenceDirectives); + function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path) { + const dirPath = getDirectoryPath(fileOrFolder); + const watches = directoryWatchesOfFailedLookups.get(dirPath); + const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); + if (isFailedLookupFile) { + invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames); + invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives); + } + return isFailedLookupFile; } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7e43d793f0b..641caa0ce57 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3543,6 +3543,18 @@ namespace ts { } } + /** + * Find the index where arrayA and arrayB differ + */ + export function findDiffIndex(arrayA: ReadonlyArray, arrayB: ReadonlyArray) { + for (let i = 0; i < arrayA.length; i++) { + if (i === arrayB.length || arrayA[i] !== arrayB[i]) { + return i; + } + } + return arrayA.length; + } + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { return host.watchFile(file, cb); } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 2a23e66763b..77c67325ef9 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -268,7 +268,7 @@ namespace ts { const resolutionCache = createResolutionCache( fileName => toPath(fileName), () => compilerOptions, - watchFailedLookupLocation, + watchDirectoryOfFailedLookupLocation, writeLog ); @@ -552,14 +552,22 @@ namespace ts { } } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog); + function watchDirectoryOfFailedLookupLocation(directory: string) { + return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog); } - function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { - updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); - resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - scheduleProgramUpdate(); + function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { + const fileOrFolderPath = toPath(fileOrFolder); + + if (configFileName) { + // Since the file existance changed, update the sourceFiles cache + (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + scheduleProgramUpdate(); + } } function watchMissingFilePath(missingFilePath: Path) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 292f6e3b90a..73912b3bd91 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -432,8 +432,8 @@ namespace ts.server { return s => this.logger.info(s + detailedInfo); } - toPath(fileName: string, basePath = this.currentDirectory) { - return toPath(fileName, basePath, this.toCanonicalFileName); + toPath(fileName: string) { + return toPath(fileName, this.currentDirectory, this.toCanonicalFileName); } /* @internal */ diff --git a/src/server/project.ts b/src/server/project.ts index ec58e4cb88d..420d080b9f2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -216,7 +216,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - (failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath), + directory => this.watchDirectoryOfFailedLookup(directory), s => this.projectService.logger.info(s), this.getProjectName(), () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined @@ -233,24 +233,30 @@ namespace ts.server { this.markAsDirty(); } - private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - return this.projectService.watchFile( + private watchDirectoryOfFailedLookup(directory: string) { + return this.projectService.watchDirectory( this.projectService.host, - failedLookupLocation, - (fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath), + directory, + fileOrFolder => this.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder), + WatchDirectoryFlags.None, WatchType.FailedLookupLocation, this ); } - private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { + private onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { + const fileOrFolderPath = this.projectService.toPath(fileOrFolder); + // There is some kind of change in the failed lookup location, update the program if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); + (this.lsHost.host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (this.resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } private setInternalCompilerOptionsForEmittingJsFiles() { From 17565d84079cec98e86fbf36b35265cfaa020bd3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 24 Aug 2017 11:50:27 -0700 Subject: [PATCH 078/144] Handle watches of missing directories and make project the module resolution host --- Jakefile.js | 1 - src/compiler/resolutionCache.ts | 85 +++--- src/compiler/sys.ts | 140 +++++---- src/compiler/utilities.ts | 4 +- src/compiler/watchedProgram.ts | 197 +++++++------ .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 116 +++++--- src/harness/virtualFileSystemWithWatch.ts | 61 +++- src/server/editorServices.ts | 83 ++++-- src/server/lsHost.ts | 137 --------- src/server/project.ts | 277 +++++++++++------- src/server/scriptInfo.ts | 2 +- src/server/session.ts | 2 +- src/server/tsconfig.json | 1 - src/server/tsconfig.library.json | 1 - src/server/typingsCache.ts | 6 +- src/server/utilities.ts | 2 +- 17 files changed, 586 insertions(+), 531 deletions(-) delete mode 100644 src/server/lsHost.ts diff --git a/Jakefile.js b/Jakefile.js index 2dcba9f97e7..46a744445d3 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -149,7 +149,6 @@ var harnessSources = harnessCoreSources.concat([ "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", - "lsHost.ts", "project.ts", "typingsCache.ts", "editorServices.ts", diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index dd03a055804..d1bd285019f 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -5,8 +5,6 @@ namespace ts { /** This is the cache of module/typedirectives resolution that can be retained across program */ export interface ResolutionCache { - setModuleResolutionHost(host: ModuleResolutionHost): void; - startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; @@ -14,8 +12,6 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean; - createHasInvalidatedResolution(): HasInvalidatedResolution; clear(): void; @@ -33,15 +29,17 @@ namespace ts { mapLocations: MultiMap; } - export function createResolutionCache( - toPath: (fileName: string) => Path, - getCompilerOptions: () => CompilerOptions, - watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher, - log: (s: string) => void, - projectName?: string, - getGlobalCache?: () => string | undefined): ResolutionCache { + export interface ResolutionCacheHost extends ModuleResolutionHost { + toPath(fileName: string): Path; + getCompilationSettings(): CompilerOptions; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback): FileWatcher; + onInvalidatedResolution(): void; + getCachedPartialSystem?(): CachedPartialSystem; + projectName?: string; + getGlobalCache?(): string | undefined; + } - let host: ModuleResolutionHost; + export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; @@ -52,23 +50,16 @@ namespace ts { const resolvedTypeReferenceDirectives = createMap>(); const directoryWatchesOfFailedLookups = createMap(); - return { - setModuleResolutionHost, startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, - onFileAddOrRemoveInDirectoryOfFailedLookup, createHasInvalidatedResolution, clear }; - function setModuleResolutionHost(updatedHost: ModuleResolutionHost) { - host = updatedHost; - } - function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); @@ -95,16 +86,16 @@ namespace ts { function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts - if (!getGlobalCache) { + if (!resolutionHost.getGlobalCache) { return primaryResult; } // otherwise try to load typings from @types - const globalCache = getGlobalCache(); + const globalCache = resolutionHost.getGlobalCache(); if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) { // create different collection of failed lookup locations for second pass // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, resolutionHost.projectName, compilerOptions, host, globalCache); if (resolvedModule) { return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as Array, failedLookupLocations) }; } @@ -123,12 +114,12 @@ namespace ts { getResultFileName: (result: R) => string | undefined, logChanges: boolean): R[] { - const path = toPath(containingFile); + const path = resolutionHost.toPath(containingFile); const currentResolutionsInFile = cache.get(path); const newResolutions: Map = createMap(); const resolvedModules: R[] = []; - const compilerOptions = getCompilerOptions(); + const compilerOptions = resolutionHost.getCompilationSettings(); for (const name of names) { // check if this is a duplicate entry in the list @@ -140,7 +131,7 @@ namespace ts { resolution = existingResolution; } else { - resolution = loader(name, containingFile, compilerOptions, host); + resolution = loader(name, containingFile, compilerOptions, resolutionHost); updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); @@ -214,12 +205,31 @@ namespace ts { const mapLocations = createMultiMap(); mapLocations.add(failedLookupLocationPath, failedLookupLocation); directoryWatchesOfFailedLookups.set(dirPath, { - watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)), + watcher: createDirectoryWatcher(getDirectoryPath(failedLookupLocation), dirPath), mapLocations }); } } + function createDirectoryWatcher(directory: string, dirPath: Path) { + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => { + const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + if (resolutionHost.getCachedPartialSystem) { + // Since the file existance changed, update the sourceFiles cache + resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (dirPath === fileOrFolderPath) { + onAddOrRemoveDirectoryOfFailedLookup(dirPath); + resolutionHost.onInvalidatedResolution(); + } + else if (onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + resolutionHost.onInvalidatedResolution(); + } + }); + } + function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { const dirPath = getDirectoryPath(failedLookupLocationPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); @@ -234,13 +244,12 @@ namespace ts { function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction, startIndex?: number) { if (failedLookupLocations) { for (let i = startIndex || 0; i < failedLookupLocations.length; i++) { - fn(failedLookupLocations[i], toPath(failedLookupLocations[i])); + fn(failedLookupLocations[i], resolutionHost.toPath(failedLookupLocations[i])); } } } function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { - log(`Resolution cache: Updating...`); const index = existingFailedLookupLocations && failedLookupLocations ? findDiffIndex(failedLookupLocations, existingFailedLookupLocations) : 0; @@ -269,7 +278,7 @@ namespace ts { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { - if (toPath(getResultFileName(result)) === deletedFilePath) { + if (resolutionHost.toPath(getResultFileName(result)) === deletedFilePath) { resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); } @@ -281,12 +290,13 @@ namespace ts { } function invalidateResolutionCacheOfChangedFailedLookupLocation( - failedLookupLocationPath: Path, - cache: Map>) { + cache: Map>, + isChangedFailedLookupLocation: (location: string) => boolean + ) { cache.forEach((value, containingFile) => { if (value) { value.forEach(resolution => { - if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) { + if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, isChangedFailedLookupLocation)) { // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); @@ -306,10 +316,17 @@ namespace ts { const watches = directoryWatchesOfFailedLookups.get(dirPath); const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); if (isFailedLookupFile) { - invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives); + const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder; + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder); + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder); } return isFailedLookupFile; } + + function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) { + const isInDirPath: (location: string) => boolean = location => getDirectoryPath(resolutionHost.toPath(location)) === dirPath; + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); + } } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 7a419be7c2d..5f3b8bd06d1 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -161,11 +161,10 @@ namespace ts { watcher.referenceCount += 1; return; } - watcher = _fs.watch( + watcher = fsWatchDirectory( dirPath || ".", - { persistent: true }, (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) - ); + ) as DirectoryWatcher; watcher.referenceCount = 1; dirWatchers.set(dirPath, watcher); return; @@ -232,6 +231,83 @@ namespace ts { const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); + function fsWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher { + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged); + return { + close: () => _fs.unwatchFile(fileName, fileChanged) + }; + + function fileChanged(curr: any, prev: any) { + const isCurrZero = +curr.mtime === 0; + const isPrevZero = +prev.mtime === 0; + const created = !isCurrZero && isPrevZero; + const deleted = isCurrZero && !isPrevZero; + + const eventKind = created + ? FileWatcherEventKind.Created + : deleted + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; + + if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) { + return; + } + + callback(fileName, eventKind); + } + } + + function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + let options: any; + let watcher = !directoryExists(directoryName) ? + watchMissingDirectory() : + watchPresentDirectory(); + return { + close: () => { + watcher.close(); + } + }; + + function watchPresentDirectory(): FileWatcher { + if (options === undefined) { + if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { + options = { persistent: true, recursive: !!recursive }; + } + else { + options = { persistent: true }; + } + } + + const dirWatcher = _fs.watch( + directoryName, + options, + callback + ); + dirWatcher.on("error", () => { + // Deleting file + watcher = watchMissingDirectory(); + // Call the callback for current directory + callback("rename", ""); + }); + return dirWatcher; + } + + function watchMissingDirectory(): FileWatcher { + return fsWatchFile(directoryName, (_fileName, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) { + watcher.close(); + watcher = watchPresentDirectory(); + // Call the callback for current directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + callback("rename", ""); + } + }); + } + } + function readFile(fileName: string, _encoding?: string): string | undefined { if (!fileExists(fileName)) { return undefined; @@ -349,7 +425,6 @@ namespace ts { return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); } - const noOpFileWatcher: FileWatcher = { close: noop }; const nodeSystem: System = { args: process.argv.slice(2), newLine: _os.EOL, @@ -367,60 +442,21 @@ namespace ts { }; } else { - _fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged); - return { - close: () => _fs.unwatchFile(fileName, fileChanged) - }; - } - - function fileChanged(curr: any, prev: any) { - const isCurrZero = +curr.mtime === 0; - const isPrevZero = +prev.mtime === 0; - const created = !isCurrZero && isPrevZero; - const deleted = isCurrZero && !isPrevZero; - - const eventKind = created - ? FileWatcherEventKind.Created - : deleted - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; - - if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) { - return; - } - - callback(fileName, eventKind); + return fsWatchFile(fileName, callback, pollingInterval); } }, watchDirectory: (directoryName, callback, recursive) => { // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - let options: any; - if (!directoryExists(directoryName)) { - // do nothing if target folder does not exist - return noOpFileWatcher; - } - - if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { - options = { persistent: true, recursive: !!recursive }; - } - else { - options = { persistent: true }; - } - - return _fs.watch( - directoryName, - options, - (eventName: string, relativeFileName: string) => { - // In watchDirectory we only care about adding and removing files (when event name is - // "rename"); changes made within files are handled by corresponding fileWatchers (when - // event name is "change") - if (eventName === "rename") { - // When deleting a file, the passed baseFileName is null - callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName))); - } + return fsWatchDirectory(directoryName, (eventName, relativeFileName) => { + // In watchDirectory we only care about adding and removing files (when event name is + // "rename"); changes made within files are handled by corresponding fileWatchers (when + // event name is "change") + if (eventName === "rename") { + // When deleting a file, the passed baseFileName is null + callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName))); } - ); + }, recursive); }, resolvePath: path => _path.resolve(path), fileExists, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 641caa0ce57..19748bfe0de 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3576,9 +3576,7 @@ namespace ts { export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - return host.watchDirectory(directory, fileName => { - cb(getNormalizedAbsolutePath(fileName, directory)); - }, recursive); + return host.watchDirectory(directory, cb, recursive); } export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 77c67325ef9..9058588ade1 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -245,7 +245,6 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files - let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; @@ -257,20 +256,47 @@ namespace ts { watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - const host = configFileName ? createCachedPartialSystem(system) : system; + const partialSystem = configFileName ? createCachedPartialSystem(system) : system; if (configFileName) { configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - // Cache for the module resolution - const resolutionCache = createResolutionCache( - fileName => toPath(fileName), - () => compilerOptions, + const getCurrentDirectory = memoize(() => partialSystem.getCurrentDirectory()); + const realpath = system.realpath && ((path: string) => system.realpath(path)); + const getCachedPartialSystem = configFileName && (() => partialSystem as CachedPartialSystem); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); + let newLine = getNewLineCharacter(compilerOptions, system); + + const compilerHost: CompilerHost & ResolutionCacheHost = { + // Members for CompilerHost + getSourceFile: getVersionedSourceFile, + getSourceFileByPath: getVersionedSourceFileByPath, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + getCurrentDirectory, + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists, + readFile, + trace, + directoryExists, + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories, + realpath, + resolveTypeReferenceDirectives, + resolveModuleNames, + onReleaseOldSourceFile, + // Members for ResolutionCacheHost + toPath, + getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation, - writeLog - ); + getCachedPartialSystem, + onInvalidatedResolution: scheduleProgramUpdate + }; + // Cache for the module resolution + const resolutionCache = createResolutionCache(compilerHost); // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); @@ -285,14 +311,15 @@ namespace ts { function synchronizeProgram() { writeLog(`Synchronizing program`); - hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); + if (hasChangedCompilerOptions) { + newLine = getNewLineCharacter(compilerOptions, system); + } + + const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) { return; } - // Create the compiler host - const compilerHost = createWatchedCompilerHost(compilerOptions); - resolutionCache.setModuleResolutionHost(compilerHost); if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } @@ -300,6 +327,7 @@ namespace ts { beforeCompile(compilerOptions); // Compile the program + compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); @@ -309,7 +337,7 @@ namespace ts { // These are the paths that program creater told us as not in use any more but were missing on the disk. // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, // if there is already watcher for it (for missing files) - // At that point our watches were updated, hence now we know that these paths are not tracked and need to be removed + // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed // so that at later time we have correct result of their presence for (const missingFilePath of missingFilePathsRequestedForRelease) { if (!missingFilesMap.has(missingFilePath)) { @@ -319,40 +347,12 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(host, program, builder); + afterCompile(partialSystem, program, builder); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } - function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const newLine = getNewLineCharacter(options, system); - const realpath = system.realpath && ((path: string) => system.realpath(path)); - - return { - getSourceFile: getVersionedSourceFile, - getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, - getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists, - readFile: fileName => system.readFile(fileName), - trace: (s: string) => system.write(s + newLine), - directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => host.getDirectories(path), - realpath, - resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false), - onReleaseOldSourceFile, - hasInvalidatedResolution - }; - } - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } function fileExists(fileName: string) { @@ -362,7 +362,31 @@ namespace ts { return !isString(hostSourceFileInfo); } - return host.fileExists(fileName); + return partialSystem.fileExists(fileName); + } + + function directoryExists(directoryName: string) { + return partialSystem.directoryExists(directoryName); + } + + function readFile(fileName: string) { + return system.readFile(fileName); + } + + function trace(s: string) { + return system.write(s + newLine); + } + + function getDirectories(path: string) { + return partialSystem.getDirectories(path); + } + + function resolveModuleNames(moduleNames: string[], containingFile: string) { + return resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false); + } + + function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { + return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); } function getDefaultLibLocation(): string { @@ -503,7 +527,7 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); needsReload = false; - const cachedHost = host as CachedPartialSystem; + const cachedHost = partialSystem as CachedPartialSystem; cachedHost.clearCache(); const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; @@ -548,26 +572,12 @@ namespace ts { function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - (host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); + (partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); } } - function watchDirectoryOfFailedLookupLocation(directory: string) { - return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog); - } - - function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { - const fileOrFolderPath = toPath(fileOrFolder); - - if (configFileName) { - // Since the file existance changed, update the sourceFiles cache - (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - // If the location results in update to failed lookup, schedule program update - if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { - scheduleProgramUpdate(); - } + function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { + return watchDirectory(system, directory, cb, WatchDirectoryFlags.None, writeLog); } function watchMissingFilePath(missingFilePath: Path) { @@ -593,42 +603,45 @@ namespace ts { updateWatchingWildcardDirectories( watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), createMapFromTemplate(configFileWildCardDirectories), - watchWildCardDirectory + watchWildcardDirectory ); } - function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) { - return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog); - } + function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { + return watchDirectory( + system, + directory, + fileOrFolder => { + Debug.assert(!!configFileName); - function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) { - Debug.assert(!!configFileName); + const fileOrFolderPath = toPath(fileOrFolder); - const fileOrFolderPath = toPath(fileOrFolder); + // Since the file existance changed, update the sourceFiles cache + (partialSystem as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + removeSourceFile(fileOrFolderPath); - // Since the file existance changed, update the sourceFiles cache - (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - removeSourceFile(fileOrFolderPath); + // If the the added or created file or folder is not supported file name, ignore the file + // But when watched directory is added/removed, we need to reload the file list + if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); + return; + } - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); - return; - } + // Reload is pending, do the reload + if (!needsReload) { + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, partialSystem); + if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { + reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + } + rootFileNames = result.fileNames; - // Reload is pending, do the reload - if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); - if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - rootFileNames = result.fileNames; - - // Schedule Update the program - scheduleProgramUpdate(); - } + // Schedule Update the program + scheduleProgramUpdate(); + } + }, + flags, + writeLog + ); } function computeHash(data: string) { diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index b7b382e5a1d..1d22dc0417e 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -147,7 +147,7 @@ namespace ts { // setting compiler options discards module resolution cache fileExistsCalled = false; - const compilerOptions = ts.cloneCompilerOptions(project.getCompilerOptions()); + const compilerOptions = ts.cloneCompilerOptions(project.getCompilationSettings()); compilerOptions.target = ts.ScriptTarget.ES5; project.setCompilerOptions(compilerOptions); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 3c394da4e31..9068afa98cf 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -321,6 +321,34 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } + function getPathsForTypesOrModules(base: string, rootPaths: string[], typesOrModules: string[], map: Map, rootDir?: string) { + while (1) { + forEach(rootPaths, r => { + const rp = combinePaths(base, r); + forEach(typesOrModules, tm => { + map.set(tm === "" ? rp : combinePaths(rp, tm), true); + }); + }); + const parentDir = getDirectoryPath(base); + if (base === rootDir || parentDir === base) { + break; + } + base = parentDir; + } + return map; + } + + function getNodeModulesWatchedDirectories(path: string, modules: string[], map = createMap()) { + forEach(modules, module => { + getPathsForTypesOrModules(path, ["node_modules"], ["", module, "@types", `@types/${module}`], map); + }); + return map; + } + + function getTypesWatchedDirectories(path: string, typeRoots: string[], types: string[], map = createMap()) { + return getPathsForTypesOrModules(path, typeRoots, types.concat(""), map, path); + } + describe("tsserverProjectSystem", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -357,8 +385,9 @@ namespace ts.projectSystem { checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); - const moduleLookupLocations = ["/a/b/c/module.ts", "/a/b/c/module.tsx"]; - checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path, ...moduleLookupLocations)); + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false); + checkWatchedDirectories(host, [], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -3403,9 +3432,9 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - assert.equal(projectService.inferredProjects[0].getCompilerOptions().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[1].getCompilerOptions().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[2].getCompilerOptions().target, ScriptTarget.ES2015); + assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); }); }); @@ -3895,13 +3924,13 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); let project = projectService.inferredProjects[0]; - let options = project.getCompilerOptions(); + let options = project.getCompilationSettings(); assert.isTrue(options.maxNodeModuleJsDepth === 2); // Assert the option sticks projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); project = projectService.inferredProjects[0]; - options = project.getCompilerOptions(); + options = project.getCompilationSettings(); assert.isTrue(options.maxNodeModuleJsDepth === 2); }); @@ -3921,15 +3950,15 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfInferredProjects(projectService, 1); let project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); projectService.openClientFile(file2.path); project = projectService.inferredProjects[0]; - assert.isTrue(project.getCompilerOptions().maxNodeModuleJsDepth === 2); + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); projectService.closeClientFile(file2.path); project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); }); }); @@ -4164,7 +4193,8 @@ namespace ts.projectSystem { describe("WatchDirectories for config file with", () => { function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { const frontendDir = "/Users/someuser/work/applications/frontend"; - const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); + const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; + const canonicalFrontendDir = toCanonical(frontendDir); const file1: FileOrFolder = { path: `${frontendDir}/src/app/utils/Analytic.ts`, content: "export class SomeClass { };" @@ -4181,6 +4211,8 @@ namespace ts.projectSystem { path: "/a/lib/lib.es2016.full.d.ts", content: libFile.content }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; const tsconfigFile: FileOrFolder = { path: `${frontendDir}/tsconfig.json`, content: JSON.stringify({ @@ -4194,16 +4226,10 @@ namespace ts.projectSystem { "noEmitOnError": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "types": [ - "node", - "jest" - ], + types, "noUnusedLocals": true, "outDir": "./compiled", - "typeRoots": [ - "types", - "node_modules/@types" - ], + typeRoots, "baseUrl": ".", "paths": { "*": [ @@ -4223,10 +4249,21 @@ namespace ts.projectSystem { const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); const projectService = createProjectService(host); - const canonicalConfigPath = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); + const canonicalConfigPath = toCanonical(tsconfigFile.path); const { configFileName } = projectService.openClientFile(file1.path); assert.equal(configFileName, tsconfigFile.path, `should find config`); checkNumberOfConfiguredProjects(projectService, 1); + const watchedModuleDirectories = arrayFrom( + getNodeModulesWatchedDirectories( + canonicalFrontendDir, + types, + getTypesWatchedDirectories( + canonicalFrontendDir, + typeRoots, + types + ) + ).keys() + ); const project = projectService.configuredProjects.get(canonicalConfigPath); verifyProjectAndWatchedDirectories(); @@ -4270,10 +4307,17 @@ namespace ts.projectSystem { verifyProjectAndWatchedDirectories(); callsTrackingHost.verifyNoHostCalls(); + function getFilePathIfOpen(f: FileOrFolder) { + const path = toCanonical(f.path); + const info = projectService.getScriptInfoForPath(toCanonical(f.path)); + return info && info.isScriptOpen() ? undefined : path; + } + function verifyProjectAndWatchedDirectories() { checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen)); checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); - checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); + checkWatchedDirectories(host, watchedModuleDirectories, /*recursive*/ false); } } @@ -4328,7 +4372,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName } = projectService.openClientFile(app.path); assert.equal(configFileName, tsconfigJson.path, `should find config`); - const watchedModuleLocations = getNodeModulesWatchedDirectories(appFolder, "lodash"); + const watchedModuleLocations = arrayFrom(getNodeModulesWatchedDirectories(appFolder, ["lodash"]).keys()); verifyProject(); let timeoutAfterReloadFs = timeoutDuringPartialInstallation; @@ -4406,7 +4450,7 @@ namespace ts.projectSystem { const lodashIndexPath = "/a/b/node_modules/@types/lodash/index.d.ts"; projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)); - watchedModuleLocations.length = watchedModuleLocations.indexOf(lodashIndexPath); + watchedModuleLocations.length = indexOf(watchedModuleLocations, getDirectoryPath(lodashIndexPath)); // npm installation complete, timeout after reload fs timeoutAfterReloadFs = true; verifyAfterPartialOrCompleteNpmInstall(2); @@ -4429,32 +4473,10 @@ namespace ts.projectSystem { const projectFilePaths = map(projectFiles, f => f.path); checkProjectActualFiles(project, projectFilePaths); - const filesWatched = filter(projectFilePaths, p => p !== app.path).concat(watchedModuleLocations); + const filesWatched = filter(projectFilePaths, p => p !== app.path); checkWatchedFiles(host, filesWatched); checkWatchedDirectories(host, [appFolder], /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } - - function getNodeModulesWatchedDirectories(path: string, module: string): string[] { - const nodeModulesDir = combinePaths(path, "node_modules/"); - const parentDir = getDirectoryPath(path); - const parentNodeModules = parentDir !== path ? getNodeModulesWatchedDirectories(parentDir, module) : []; - return [ - `${nodeModulesDir}${module}.ts`, - `${nodeModulesDir}${module}.tsx`, - `${nodeModulesDir}${module}.d.ts`, - `${nodeModulesDir}${module}/index.ts`, - `${nodeModulesDir}${module}/index.tsx`, - `${nodeModulesDir}${module}/index.d.ts`, - `${nodeModulesDir}@types/${module}.d.ts`, - `${nodeModulesDir}@types/${module}/index.d.ts`, - `${nodeModulesDir}@types/${module}/package.json`, - `${nodeModulesDir}${module}.js`, - `${nodeModulesDir}${module}.jsx`, - `${nodeModulesDir}${module}/package.json`, - `${nodeModulesDir}${module}/index.js`, - `${nodeModulesDir}${module}/index.jsx`, - ].concat(parentNodeModules); + checkWatchedDirectories(host, watchedModuleLocations, /*recursive*/ false); } } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 44beb83922f..4b249c78e15 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -95,8 +95,35 @@ namespace ts.TestFSWithWatch { } } + function getDiffInKeys(map: Map, expectedKeys: string[]) { + if (map.size === expectedKeys.length) { + return ""; + } + const notInActual: string[] = []; + const duplicates: string[] = []; + const seen = createMap(); + forEach(expectedKeys, expectedKey => { + if (seen.has(expectedKey)) { + duplicates.push(expectedKey); + return; + } + seen.set(expectedKey, true); + if (!map.has(expectedKey)) { + notInActual.push(expectedKey); + } + }); + const inActualNotExpected: string[] = []; + map.forEach((_value, key) => { + if (!seen.has(key)) { + inActualNotExpected.push(key); + } + seen.set(key, true); + }); + return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`; + } + function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`); + assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`); for (const name of expectedKeys) { assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } @@ -175,6 +202,8 @@ namespace ts.TestFSWithWatch { type TimeOutCallback = () => any; + export type TestFileWatcher = { cb: FileWatcherCallback; fileName: string; }; + export type TestDirectoryWatcher = { cb: DirectoryWatcherCallback; directoryName: string; }; export class TestServerHost implements server.ServerHost { args: string[] = []; @@ -186,9 +215,9 @@ namespace ts.TestFSWithWatch { private timeoutCallbacks = new Callbacks(); private immediateCallbacks = new Callbacks(); - readonly watchedDirectories = createMultiMap(); - readonly watchedDirectoriesRecursive = createMultiMap(); - readonly watchedFiles = createMultiMap(); + readonly watchedDirectories = createMultiMap(); + readonly watchedDirectoriesRecursive = createMultiMap(); + readonly watchedFiles = createMultiMap(); constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -317,8 +346,8 @@ namespace ts.TestFSWithWatch { // Invoke directory and recursive directory watcher for the folder // Here we arent invoking recursive directory watchers for the base folders // since that is something we would want to do for both file as well as folder we are deleting - invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => cb(relativePath)); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); } if (basePath !== fileOrFolder.path) { @@ -333,8 +362,7 @@ namespace ts.TestFSWithWatch { private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) { const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - const fileName = getBaseFileName(fileFullPath); - invokeWatcherCallbacks(callbacks, cb => cb(fileName, eventKind)); + invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { @@ -346,16 +374,20 @@ namespace ts.TestFSWithWatch { */ private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName); - invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath)); this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); } + private directoryCallback({ cb, directoryName }: TestDirectoryWatcher, relativePath: string) { + cb(combinePaths(directoryName, relativePath)); + } + /** * This will call the recursive directory watcher for this directory as well as all the base directories */ private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { const relativePath = this.getRelativePathToDirectory(fullPath, fileName); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => this.directoryCallback(cb, relativePath)); const basePath = getDirectoryPath(fullPath); if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { this.invokeRecursiveDirectoryWatcher(basePath, fileName); @@ -437,9 +469,13 @@ namespace ts.TestFSWithWatch { }); } - watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { + watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toFullPath(directoryName); const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; + const callback: TestDirectoryWatcher = { + cb, + directoryName + }; map.add(path, callback); return { referenceCount: 0, @@ -452,8 +488,9 @@ namespace ts.TestFSWithWatch { return Harness.mockHash(s); } - watchFile(fileName: string, callback: FileWatcherCallback) { + watchFile(fileName: string, cb: FileWatcherCallback) { const path = this.toFullPath(fileName); + const callback: TestFileWatcher = { fileName, cb }; this.watchedFiles.add(path, callback); return { close: () => this.watchedFiles.remove(path, callback) }; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 73912b3bd91..a85ebebec5e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -3,7 +3,6 @@ /// /// /// -/// /// /// @@ -476,7 +475,6 @@ namespace ts.server { this.typingsCache.deleteTypingsForProject(response.projectName); break; } - project.markAsDirty(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -528,6 +526,7 @@ namespace ts.server { /* @internal */ delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { + project.markAsDirty(); this.delayUpdateProjectGraph(project); this.delayInferredProjectsRefresh(); } @@ -712,36 +711,56 @@ namespace ts.server { } } - /* @internal */ - onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: string) { - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); - project.updateTypes(); - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + /*@internal*/ + watchTypeRootDirectory(root: Path, project: ConfiguredProject) { + // TODO: This is not needed anymore with watches for failed lookup locations? + return this.watchDirectory( + this.host, + root, + fileOrFolder => { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + project.updateTypes(); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + }, + WatchDirectoryFlags.None, + WatchType.TypeRoot, + project + ); } /** - * This is the callback function when a watched directory has added or removed source code files. - * @param project the project that associates with this directory watcher - * @param fileName the absolute file name that changed in watched directory + * This is to watch whenever files are added or removed to the wildcard directories */ - /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: string) { - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); - const configFilename = project.getConfigFilePath(); + /*@internal*/ + watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject) { + return this.watchDirectory( + this.host, + directory, + fileOrFolder => { + const fileOrFolderPath = this.toPath(fileOrFolder); + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + const configFilename = project.getConfigFilePath(); - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { - this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); - return; - } + // If the the added or created file or folder is not supported file name, ignore the file + // But when watched directory is added/removed, we need to reload the file list + if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); + return; + } - const configFileSpecs = project.configFileSpecs; - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); - project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); - this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + // Reload is pending, do the reload + if (!project.pendingReload) { + const configFileSpecs = project.configFileSpecs; + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); + project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); + this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + } + }, + flags, + WatchType.WildcardDirectories, + project + ); } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { @@ -1253,7 +1272,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: PartialSystem) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedPartialSystem: CachedPartialSystem) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1265,7 +1284,7 @@ namespace ts.server { const errors = result.parseDiagnostics; const parsedCommandLine = parseJsonSourceFileConfigFileContent( result, - cachedServerHost, + cachedPartialSystem, getDirectoryPath(configFilename), /*existingOptions*/ {}, configFilename, @@ -1350,7 +1369,7 @@ namespace ts.server { const data: ProjectInfoTelemetryEventData = { projectId: this.host.createHash(projectKey), fileStats: countEachFileTypes(project.getScriptInfos()), - compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilerOptions()), + compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilationSettings()), typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()), extends: projectOptions && projectOptions.configHasExtendsProperty, files: projectOptions && projectOptions.configHasFilesProperty, @@ -1435,8 +1454,8 @@ namespace ts.server { 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)) { + // Use the project's fileExists so that it can use caching instead of reaching to disk for the query + if (!project.fileExists(newRootFile)) { path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName); const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { @@ -1448,7 +1467,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.partialSystem); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts deleted file mode 100644 index b758cd796bf..00000000000 --- a/src/server/lsHost.ts +++ /dev/null @@ -1,137 +0,0 @@ -/// -/// -/// -/// - -namespace ts.server { - export class LSHost implements LanguageServiceHost, ModuleResolutionHost { - /*@internal*/ - compilationSettings: CompilerOptions; - - readonly trace: (s: string) => void; - readonly realpath?: (path: string) => string; - - /*@internal*/ - hasInvalidatedResolution: HasInvalidatedResolution; - - /** - * This is the host that is associated with the project. This is normally same as projectService's host - * except in Configured projects where it is CachedServerHost so that we can cache the results of the - * file system entries as we would anyways be watching files in the project (so safe to cache) - */ - /*@internal*/ - host: PartialSystem; - - constructor(host: PartialSystem, private project: Project, private readonly cancellationToken: HostCancellationToken) { - this.host = host; - this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - - const serverHost = this.getServerHost(); - if (serverHost.trace) { - this.trace = s => serverHost.trace(s); - } - - if (serverHost.realpath) { - this.realpath = path => serverHost.realpath(path); - } - } - - private getServerHost() { - return this.project.projectService.host; - } - - dispose() { - this.project = undefined; - this.host = undefined; - } - - getNewLine() { - return this.host.newLine; - } - - getProjectVersion() { - return this.project.getProjectVersion(); - } - - getCompilationSettings() { - return this.compilationSettings; - } - - useCaseSensitiveFileNames() { - return this.host.useCaseSensitiveFileNames; - } - - getCancellationToken() { - return this.cancellationToken; - } - - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return this.project.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); - } - - resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { - return this.project.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); - } - - getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.getServerHost().getExecutingFilePath())); - return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); - } - - getScriptSnapshot(filename: string): IScriptSnapshot { - const scriptInfo = this.project.getScriptInfoLSHost(filename); - if (scriptInfo) { - return scriptInfo.getSnapshot(); - } - } - - getScriptFileNames() { - return this.project.getRootFilesLSHost(); - } - - getTypeRootsVersion() { - return this.project.typesVersion; - } - - getScriptKind(fileName: string) { - const info = this.project.getScriptInfoLSHost(fileName); - return info && info.scriptKind; - } - - getScriptVersion(filename: string) { - const info = this.project.getScriptInfoLSHost(filename); - return info && info.getLatestVersion(); - } - - getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); - } - - resolvePath(path: string): string { - return this.getServerHost().resolvePath(path); - } - - fileExists(file: string): boolean { - // As an optimization, don't hit the disks for files we already know don't exist - // (because we're watching for their creation). - const path = this.project.projectService.toPath(file); - return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file); - } - - readFile(fileName: string): string | undefined { - return this.host.readFile(fileName); - } - - directoryExists(path: string): boolean { - return this.host.directoryExists(path); - } - - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return this.host.readDirectory(path, extensions, exclude, include, depth); - } - - getDirectories(path: string): string[] { - return this.host.getDirectories(path); - } - } -} diff --git a/src/server/project.ts b/src/server/project.ts index 420d080b9f2..bf73d4d175c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1,7 +1,7 @@ /// /// /// -/// +/// /// /// @@ -112,7 +112,7 @@ namespace ts.server { return value instanceof ScriptInfo; } - export abstract class Project { + export abstract class Project implements LanguageServiceHost, ModuleResolutionHost { private rootFiles: ScriptInfo[] = []; private rootFilesMap: Map = createMap(); private program: Program; @@ -127,11 +127,14 @@ namespace ts.server { public languageServiceEnabled = true; - /*@internal*/ - resolutionCache: ResolutionCache; + readonly trace?: (s: string) => void; + readonly realpath?: (path: string) => string; /*@internal*/ - lsHost: LSHost; + hasInvalidatedResolution: HasInvalidatedResolution; + + /*@internal*/ + resolutionCache: ResolutionCache; private builder: Builder; /** @@ -161,7 +164,7 @@ namespace ts.server { private typingFiles: SortedReadonlyArray; - public typesVersion = 0; + private typesVersion = 0; public isNonTsProject() { this.updateGraph(); @@ -190,7 +193,7 @@ namespace ts.server { } constructor( - private readonly projectName: string, + /*@internal*/readonly projectName: string, readonly projectKind: ProjectKind, readonly projectService: ProjectService, private documentRegistry: DocumentRegistry, @@ -198,7 +201,7 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - host: PartialSystem) { + /*@internal*/public partialSystem: PartialSystem) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -211,52 +214,162 @@ namespace ts.server { } this.setInternalCompilerOptionsForEmittingJsFiles(); + const host = this.projectService.host; + if (host.trace) { + this.trace = s => host.trace(s); + } - this.lsHost = new LSHost(host, this, this.projectService.cancellationToken); - this.resolutionCache = createResolutionCache( - fileName => this.projectService.toPath(fileName), - () => this.compilerOptions, - directory => this.watchDirectoryOfFailedLookup(directory), - s => this.projectService.logger.info(s), - this.getProjectName(), - () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined - ); - this.lsHost.compilationSettings = this.compilerOptions; - this.resolutionCache.setModuleResolutionHost(this.lsHost); - - this.languageService = createLanguageService(this.lsHost, this.documentRegistry); + if (host.realpath) { + this.realpath = path => host.realpath(path); + } + this.languageService = createLanguageService(this, this.documentRegistry); if (!languageServiceEnabled) { this.disableLanguageService(); } - + this.resolutionCache = createResolutionCache(this); this.markAsDirty(); } - private watchDirectoryOfFailedLookup(directory: string) { + getCompilationSettings() { + return this.compilerOptions; + } + + getNewLine() { + return this.partialSystem.newLine; + } + + getProjectVersion() { + return this.projectStateVersion.toString(); + } + + getScriptFileNames() { + const result: string[] = []; + if (this.rootFiles) { + this.rootFilesMap.forEach((value, _path) => { + const f: ScriptInfo = isScriptInfo(value) && value; + if (this.languageServiceEnabled || (f && f.isScriptOpen())) { + // if language service is disabled - process only files that are open + result.push(f ? f.fileName : value as NormalizedPath); + } + }); + if (this.typingFiles) { + for (const f of this.typingFiles) { + result.push(f); + } + } + } + return result; + } + + private getScriptInfoLSHost(fileName: string) { + const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.partialSystem); + if (scriptInfo) { + const existingValue = this.rootFilesMap.get(scriptInfo.path); + if (existingValue !== undefined && existingValue !== scriptInfo) { + // This was missing path earlier but now the file exists. Update the root + this.rootFiles.push(scriptInfo); + this.rootFilesMap.set(scriptInfo.path, scriptInfo); + } + scriptInfo.attachToProject(this); + } + return scriptInfo; + } + + getScriptKind(fileName: string) { + const info = this.getScriptInfoLSHost(fileName); + return info && info.scriptKind; + } + + getScriptVersion(filename: string) { + const info = this.getScriptInfoLSHost(filename); + return info && info.getLatestVersion(); + } + + getScriptSnapshot(filename: string): IScriptSnapshot { + const scriptInfo = this.getScriptInfoLSHost(filename); + if (scriptInfo) { + return scriptInfo.getSnapshot(); + } + } + + getCancellationToken() { + return this.projectService.cancellationToken; + } + + getCurrentDirectory(): string { + return this.partialSystem.getCurrentDirectory(); + } + + getDefaultLibFileName() { + const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.host.getExecutingFilePath())); + return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions)); + } + + useCaseSensitiveFileNames() { + return this.partialSystem.useCaseSensitiveFileNames; + } + + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { + return this.partialSystem.readDirectory(path, extensions, exclude, include, depth); + } + + readFile(fileName: string): string | undefined { + return this.partialSystem.readFile(fileName); + } + + fileExists(file: string): boolean { + // As an optimization, don't hit the disks for files we already know don't exist + // (because we're watching for their creation). + const path = this.toPath(file); + return !this.isWatchedMissingFile(path) && this.partialSystem.fileExists(file); + } + + getTypeRootsVersion() { + return this.typesVersion; + } + + resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); + } + + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { + return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); + } + + directoryExists(path: string): boolean { + return this.partialSystem.directoryExists(path); + } + + getDirectories(path: string): string[] { + return this.partialSystem.getDirectories(path); + } + + /*@internal*/ + toPath(fileName: string) { + return this.projectService.toPath(fileName); + } + + /*@internal*/ + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { return this.projectService.watchDirectory( this.projectService.host, directory, - fileOrFolder => this.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder), + cb, WatchDirectoryFlags.None, WatchType.FailedLookupLocation, this ); } - private onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { - const fileOrFolderPath = this.projectService.toPath(fileOrFolder); + /*@internal*/ + onInvalidatedResolution() { + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + } - // There is some kind of change in the failed lookup location, update the program - if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - // If the location results in update to failed lookup, schedule program update - if (this.resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); - } + /*@internal*/ + getGlobalCache() { + return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } private setInternalCompilerOptionsForEmittingJsFiles() { @@ -326,10 +439,6 @@ namespace ts.server { return this.builder.getChangedProgramFiles(this.program); } - getProjectVersion() { - return this.projectStateVersion.toString(); - } - enableLanguageService() { if (this.languageServiceEnabled) { return; @@ -366,7 +475,6 @@ namespace ts.server { updateTypes() { this.typesVersion++; - this.markAsDirty(); } close() { @@ -395,8 +503,7 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; - this.lsHost.dispose(); - this.lsHost = undefined; + this.partialSystem = undefined; // Clean up file watchers waiting for missing files if (this.missingFilesMap) { @@ -410,11 +517,7 @@ namespace ts.server { } isClosed() { - return this.lsHost === undefined; - } - - getCompilerOptions() { - return this.compilerOptions; + return this.rootFiles === undefined; } hasRoots() { @@ -425,25 +528,6 @@ namespace ts.server { return this.rootFiles && this.rootFiles.map(info => info.fileName); } - getRootFilesLSHost() { - const result: string[] = []; - if (this.rootFiles) { - this.rootFilesMap.forEach((value, _path) => { - const f: ScriptInfo = isScriptInfo(value) && value; - if (this.languageServiceEnabled || (f && f.isScriptOpen())) { - // if language service is disabled - process only files that are open - result.push(f ? f.fileName : value as NormalizedPath); - } - }); - if (this.typingFiles) { - for (const f of this.typingFiles) { - result.push(f); - } - } - } - return result; - } - /*@internal*/ getRootFilesMap() { return this.rootFilesMap; @@ -622,7 +706,7 @@ namespace ts.server { */ updateGraph(): boolean { this.resolutionCache.startRecordingFilesWithChangedResolutions(); - this.lsHost.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); + this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); let hasChanges = this.updateGraphWorker(); @@ -659,7 +743,7 @@ namespace ts.server { // Note we are retaining builder so we can send events for project change if (this.builder) { if (this.languageServiceEnabled) { - this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution); + this.builder.onProgramUpdateGraph(this.program, this.hasInvalidatedResolution); } else { this.builder.clear(); @@ -720,7 +804,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host); + const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.partialSystem); scriptInfo.attachToProject(this); }, removed => { @@ -739,7 +823,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); + (this.partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { @@ -747,7 +831,6 @@ namespace ts.server { fileWatcher.close(); // When a missing file is created, we should update the graph. - this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } }, @@ -757,28 +840,14 @@ namespace ts.server { return fileWatcher; } - isWatchedMissingFile(path: Path) { + private isWatchedMissingFile(path: Path) { return this.missingFilesMap && this.missingFilesMap.has(path); } - getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host); - if (scriptInfo) { - const existingValue = this.rootFilesMap.get(scriptInfo.path); - if (existingValue !== undefined && existingValue !== scriptInfo) { - // This was missing path earlier but now the file exists. Update the root - this.rootFiles.push(scriptInfo); - this.rootFilesMap.set(scriptInfo.path, scriptInfo); - } - scriptInfo.attachToProject(this); - } - return scriptInfo; - } - getScriptInfoForNormalizedPath(fileName: NormalizedPath) { const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath( fileName, /*openedByClient*/ false, /*fileContent*/ undefined, - /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.partialSystem ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); @@ -815,8 +884,6 @@ namespace ts.server { if (changesAffectModuleResolution(oldOptions, compilerOptions)) { this.resolutionCache.clear(); } - this.lsHost.compilationSettings = this.compilerOptions; - this.markAsDirty(); } } @@ -839,7 +906,7 @@ namespace ts.server { projectName: this.getProjectName(), version: this.projectStructureVersion, isInferred: this.projectKind === ProjectKind.Inferred, - options: this.getCompilerOptions(), + options: this.getCompilationSettings(), languageServiceDisabled: !this.languageServiceEnabled }; const updatedFileNames = this.updatedFileNames; @@ -913,7 +980,7 @@ namespace ts.server { setCompilerOptions(options?: CompilerOptions) { // Avoid manipulating the given options directly - const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions(); + const newOptions = options ? cloneCompilerOptions(options) : this.getCompilationSettings(); if (!newOptions) { return; } @@ -1042,7 +1109,7 @@ namespace ts.server { /*@internal*/ getCachedPartialSystem() { - return this.lsHost.host as CachedPartialSystem; + return this.partialSystem as CachedPartialSystem; } getConfigFilePath() { @@ -1051,7 +1118,7 @@ namespace ts.server { enablePlugins() { const host = this.projectService.host; - const options = this.getCompilerOptions(); + const options = this.getCompilationSettings(); if (!host.require) { this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded"); @@ -1113,7 +1180,7 @@ namespace ts.server { config: configEntry, project: this, languageService: this.languageService, - languageServiceHost: this.lsHost, + languageServiceHost: this, serverHost: this.projectService.host }; @@ -1174,14 +1241,7 @@ namespace ts.server { this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), wildcardDirectories, // Create new directory watcher - (directory, flags) => this.projectService.watchDirectory( - this.projectService.host, - directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - flags, - WatchType.WildcardDirectories, - this - ) + (directory, flags) => this.projectService.watchWildcardDirectory(directory as Path, flags, this), ); } @@ -1201,14 +1261,7 @@ namespace ts.server { newTypeRoots, { // Create new watch - createNewValue: root => this.projectService.watchDirectory( - this.projectService.host, - root, - path => this.projectService.onTypeRootFileChanged(this, path), - WatchDirectoryFlags.None, - WatchType.TypeRoot, - this - ), + createNewValue: root => this.projectService.watchTypeRootDirectory(root as Path, this), // Close existing watch thats not needed any more onDeleteValue: closeFileWatcher } @@ -1245,7 +1298,7 @@ namespace ts.server { } getEffectiveTypeRoots() { - return getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; + return getEffectiveTypeRoots(this.getCompilationSettings(), this.partialSystem) || []; } /*@internal*/ diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index c35f26e3d3c..d76f930f5ec 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -237,7 +237,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.lsHost.host as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + (p.partialSystem as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways diff --git a/src/server/session.ts b/src/server/session.ts index 4b42aaba96a..b4f2b46f99c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1218,7 +1218,7 @@ namespace ts.server { result.push({ projectFileName: project.getProjectName(), fileNames: project.getCompileOnSaveAffectedFileList(info), - projectUsesOutFile: !!project.getCompilerOptions().outFile || !!project.getCompilerOptions().out + projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out }); } } diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index b0eb4965ea6..c838a7209d9 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -15,7 +15,6 @@ "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", - "lsHost.ts", "typingsCache.ts", "project.ts", "editorServices.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index b59395fda27..aa3148d58a1 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -16,7 +16,6 @@ }, "files": [ "editorServices.ts", - "lsHost.ts", "project.ts", "protocol.ts", "scriptInfo.ts", diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 3ef37685da2..207824616a9 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -89,12 +89,12 @@ namespace ts.server { if (forceRefresh || !entry || typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) || - compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions) || + compilerOptionsChanged(project.getCompilationSettings(), entry.compilerOptions) || unresolvedImportsChanged(unresolvedImports, entry.unresolvedImports)) { // Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options. // instead it acts as a placeholder to prevent issuing multiple requests this.perProjectCache.set(project.getProjectName(), { - compilerOptions: project.getCompilerOptions(), + compilerOptions: project.getCompilationSettings(), typeAcquisition, typings: result, unresolvedImports, @@ -125,4 +125,4 @@ namespace ts.server { this.installer.onProjectClosed(project); } } -} \ No newline at end of file +} diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 3ae1dd1d71d..c596176ad86 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -50,7 +50,7 @@ namespace ts.server { return { projectName: project.getProjectName(), fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true), - compilerOptions: project.getCompilerOptions(), + compilerOptions: project.getCompilationSettings(), typeAcquisition, unresolvedImports, projectRootPath: getProjectRootPath(project), From 10ea5bf4607fd07b0ad23c6b6c410668ddd5d782 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 25 Aug 2017 20:20:14 -0700 Subject: [PATCH 079/144] Script infos while opening/closing shouldnt mark project as dirty if the contents dont change --- .../unittests/cachingInServerLSHost.ts | 2 +- src/harness/unittests/textStorage.ts | 12 +- src/server/editorServices.ts | 96 ++++++----- src/server/project.ts | 9 +- src/server/scriptInfo.ts | 162 +++++++++++------- src/server/scriptVersionCache.ts | 21 +-- 6 files changed, 168 insertions(+), 134 deletions(-) diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 1d22dc0417e..a1095e7dc48 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -61,7 +61,7 @@ namespace ts { typingsInstaller: undefined }; const projectService = new server.ProjectService(svcOpts); - const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, serverHost); + const rootScriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(server.toNormalizedPath(rootFile), /*openedByClient*/ true); const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); diff --git a/src/harness/unittests/textStorage.ts b/src/harness/unittests/textStorage.ts index 545d090df9b..aa8231aa31a 100644 --- a/src/harness/unittests/textStorage.ts +++ b/src/harness/unittests/textStorage.ts @@ -21,7 +21,7 @@ namespace ts.textStorage { const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path)); const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path)); - ts1.useScriptVersionCache(); + ts1.useScriptVersionCache_TestOnly(); ts2.useText(); const lineMap = computeLineStarts(f.content); @@ -55,16 +55,16 @@ namespace ts.textStorage { const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path)); ts1.getSnapshot(); - assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 1"); + assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1"); ts1.edit(0, 5, " "); - assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 1"); + assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 1"); ts1.useText(); - assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 2"); + assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2"); ts1.getLineInfo(0); - assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 2"); + assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2"); }); }); -} \ No newline at end of file +} diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a85ebebec5e..9195083bb68 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -687,7 +687,7 @@ namespace ts.server { else { // file has been changed which might affect the set of referenced files in projects that include // this file and set of inferred projects - info.reloadFromFile(); + info.delayReloadNonMixedContentFile(); this.delayUpdateProjectGraphs(info.containingProjects); } } @@ -753,7 +753,7 @@ namespace ts.server { const configFileSpecs = project.configFileSpecs; const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); - this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); + this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } }, @@ -1352,7 +1352,7 @@ namespace ts.server { /*languageServiceEnabled*/ !this.exceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader), options.compileOnSave === undefined ? true : options.compileOnSave); - this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, /*clientFileName*/ undefined, typeAcquisition); + this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition); this.externalProjects.push(project); this.sendProjectTelemetry(project.externalProjectName, project); return project; @@ -1401,14 +1401,14 @@ namespace ts.server { } } - private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition): void { - this.updateNonInferredProjectFiles(project, files, propertyReader, clientFileName); + private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, typeAcquisition: TypeAcquisition): void { + this.updateNonInferredProjectFiles(project, files, propertyReader); project.setTypeAcquisition(typeAcquisition); // This doesnt need scheduling since its either creation or reload of the project project.updateGraph(); } - private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) { + private createConfiguredProject(configFileName: NormalizedPath) { const cachedPartialSystem = createCachedPartialSystem(this.host); const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedPartialSystem); this.logger.info(`Opened configuration file ${configFileName}`); @@ -1438,14 +1438,14 @@ namespace ts.server { } project.setProjectErrors(configFileErrors); - this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition); + this.addFilesToNonInferredProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, projectOptions.typeAcquisition); this.configuredProjects.set(project.canonicalConfigFilePath, project); this.setConfigFileExistenceByNewConfiguredProject(project); this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); return project; } - private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader, clientFileName?: string) { + private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap = createMap(); @@ -1467,7 +1467,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.partialSystem); + scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.partialSystem); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1509,7 +1509,7 @@ namespace ts.server { if (compileOnSave !== undefined) { project.compileOnSaveEnabled = compileOnSave; } - this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, /*clientFileName*/ undefined, newTypeAcquisition); + this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition); } /** @@ -1617,15 +1617,11 @@ namespace ts.server { return project; } - /** - * @param uncheckedFileName is absolute pathname - * @param fileContent is a known version of the file content that is more up to date than the one on disk - */ /*@internal*/ - getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: PartialSystem) { - return this.getOrCreateScriptInfoForNormalizedPath( - toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined, - /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn + getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, hostToQueryFileExistsOn: PartialSystem) { + return this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath( + toNormalizedPath(uncheckedFileName), /*scriptKind*/ undefined, + /*hasMixedContent*/ undefined, hostToQueryFileExistsOn ); } @@ -1654,38 +1650,41 @@ namespace ts.server { } } + getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + } + + getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); + } + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content"); const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { - if (openedByClient || (hostToQueryFileExistsOn || this.host).fileExists(fileName)) { - info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path); - - this.filenameToScriptInfo.set(info.path, info); - - if (openedByClient) { - if (fileContent === undefined) { - // if file is opened by client and its content is not specified - use file text - fileContent = this.host.readFile(fileName) || ""; - } - } - else { - this.watchClosedScriptInfo(info); - } + // If the file is not opened by client and the file doesnot exist on the disk, return + if (!openedByClient && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) { + return; + } + info = new ScriptInfo(this.host, fileName, scriptKind, hasMixedContent, path); + this.filenameToScriptInfo.set(info.path, info); + if (!openedByClient) { + this.watchClosedScriptInfo(info); } } - if (info) { - if (openedByClient && !info.isScriptOpen()) { - this.stopWatchingScriptInfo(info); - info.open(fileContent); - if (hasMixedContent) { - info.registerFileUpdate(); - } - } - else if (fileContent !== undefined) { - info.reload(fileContent); + if (openedByClient && !info.isScriptOpen()) { + // Opening closed script info + // either it was created just now, or was part of projects but was closed + this.stopWatchingScriptInfo(info); + info.open(fileContent); + if (hasMixedContent) { + info.registerFileUpdate(); } } + else { + Debug.assert(fileContent === undefined); + } return info; } @@ -1730,9 +1729,16 @@ namespace ts.server { /** * This function rebuilds the project for every file opened by the client + * This does not reload contents of open files from disk. But we could do that if needed */ reloadProjects() { this.logger.info("reload projects."); + // If we want this to also reload open files from disk, we could do that, + // but then we need to make sure we arent calling this function + // (and would separate out below reloading of projects to be called when immediate reload is needed) + // as there is no need to load contents of the files from the disk + + // Reload Projects this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false); this.refreshInferredProjects(); } @@ -1771,7 +1777,7 @@ namespace ts.server { if (configFileName) { const project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - this.createConfiguredProject(configFileName, info.fileName); + this.createConfiguredProject(configFileName); updatedProjects.set(configFileName, true); } else if (!updatedProjects.has(configFileName)) { @@ -1862,14 +1868,14 @@ namespace ts.server { let configFileName: NormalizedPath; let configFileErrors: ReadonlyArray; - const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); + const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, fileContent, scriptKind, hasMixedContent); let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); if (!project) { configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); if (!project) { - project = this.createConfiguredProject(configFileName, fileName); + project = this.createConfiguredProject(configFileName); // even if opening config file was successful, it could still // contain errors that were tolerated. diff --git a/src/server/project.ts b/src/server/project.ts index bf73d4d175c..1f6b762b674 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -263,7 +263,7 @@ namespace ts.server { } private getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.partialSystem); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.partialSystem); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); if (existingValue !== undefined && existingValue !== scriptInfo) { @@ -804,7 +804,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.partialSystem); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.partialSystem); scriptInfo.attachToProject(this); }, removed => { @@ -845,9 +845,8 @@ namespace ts.server { } getScriptInfoForNormalizedPath(fileName: NormalizedPath) { - const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath( - fileName, /*openedByClient*/ false, /*fileContent*/ undefined, - /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.partialSystem + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath( + fileName, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.partialSystem ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index d76f930f5ec..bc3e11baa0e 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -4,13 +4,19 @@ namespace ts.server { /* @internal */ export class TextStorage { + // Generated only on demand and removes the text if any edits private svc: ScriptVersionCache | undefined; private svcVersion = 0; + // Store text when there is no svc or svc has no change, on edit, remove the text private text: string; private lineMap: number[]; private textVersion = 0; + public isOpen: boolean; + private ownFileText: boolean; + private pendingReloadFromDisk: boolean; + constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) { } @@ -20,43 +26,75 @@ namespace ts.server { : `Text-${this.textVersion}`; } - public hasScriptVersionCache() { + public hasScriptVersionCache_TestOnly() { return this.svc !== undefined; } - public useScriptVersionCache(newText?: string) { - this.switchToScriptVersionCache(newText); + public useScriptVersionCache_TestOnly() { + this.switchToScriptVersionCache(); } public useText(newText?: string) { this.svc = undefined; - this.setText(newText); + this.text = newText; + this.lineMap = undefined; + this.textVersion++; } public edit(start: number, end: number, newText: string) { this.switchToScriptVersionCache().edit(start, end - start, newText); + this.ownFileText = false; + this.text = undefined; + this.lineMap = undefined; } - public reload(text: string) { - if (this.svc) { - this.svc.reload(text); - } - else { - this.setText(text); + /** returns true if text changed */ + public reload(newText: string) { + Debug.assert(newText !== undefined); + + // Reload always has fresh content + this.pendingReloadFromDisk = false; + + // If text changed set the text + // This also ensures that if we had switched to version cache, + // we are switching back to text. + // The change to version cache will happen when needed + // Thus avoiding the computation if there are no changes + if (this.text !== newText) { + this.useText(newText); + // We cant guarantee new text is own file text + this.ownFileText = false; + return true; } } - public reloadFromFile(tempFileName?: string) { - if (this.svc || (tempFileName !== this.fileName)) { - this.reload(this.getFileText(tempFileName)); + /** returns true if text changed */ + public reloadFromDisk() { + let reloaded = false; + if (!this.pendingReloadFromDisk && !this.ownFileText) { + reloaded = this.reload(this.getFileText()); + this.ownFileText = true; } - else { - this.setText(undefined); + return reloaded; + } + + public delayReloadFromFileIntoText() { + this.pendingReloadFromDisk = true; + } + + /** returns true if text changed */ + public reloadFromFile(tempFileName: string) { + let reloaded = false; + // Reload if different file or we dont know if we are working with own file text + if (tempFileName !== this.fileName || !this.ownFileText) { + reloaded = this.reload(this.getFileText(tempFileName)); + this.ownFileText = !tempFileName || tempFileName === this.fileName; } + return reloaded; } public getSnapshot(): IScriptSnapshot { - return this.svc + return this.useScriptVersionCacheIfValidOrOpen() ? this.svc.getSnapshot() : ScriptSnapshot.fromString(this.getOrLoadText()); } @@ -68,7 +106,7 @@ namespace ts.server { * @param line 0 based index */ lineToTextSpan(line: number): TextSpan { - if (!this.svc) { + if (!this.useScriptVersionCacheIfValidOrOpen()) { const lineMap = this.getLineMap(); const start = lineMap[line]; // -1 since line is 1-based const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length; @@ -82,7 +120,7 @@ namespace ts.server { * @param offset 1 based index */ lineOffsetToPosition(line: number, offset: number): number { - if (!this.svc) { + if (!this.useScriptVersionCacheIfValidOrOpen()) { return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text); } @@ -91,7 +129,7 @@ namespace ts.server { } positionToLineOffset(position: number): protocol.Location { - if (!this.svc) { + if (!this.useScriptVersionCacheIfValidOrOpen()) { const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position); return { line: line + 1, offset: character + 1 }; } @@ -102,43 +140,39 @@ namespace ts.server { return this.host.readFile(tempFileName || this.fileName) || ""; } - private ensureNoScriptVersionCache() { - Debug.assert(!this.svc, "ScriptVersionCache should not be set"); - } - - private switchToScriptVersionCache(newText?: string): ScriptVersionCache { - if (!this.svc) { - this.svc = ScriptVersionCache.fromString(newText !== undefined ? newText : this.getOrLoadText()); + private switchToScriptVersionCache(): ScriptVersionCache { + if (!this.svc || this.pendingReloadFromDisk) { + this.svc = ScriptVersionCache.fromString(this.getOrLoadText()); this.svcVersion++; - this.text = undefined; } return this.svc; } + private useScriptVersionCacheIfValidOrOpen(): ScriptVersionCache | undefined { + // If this is open script, use the cache + if (this.isOpen) { + return this.switchToScriptVersionCache(); + } + + // Else if the svc is uptodate with the text, we are good + return !this.pendingReloadFromDisk && this.svc; + } + private getOrLoadText() { - this.ensureNoScriptVersionCache(); - if (this.text === undefined) { - this.setText(this.getFileText()); + if (this.text === undefined || this.pendingReloadFromDisk) { + Debug.assert(!this.svc || this.pendingReloadFromDisk, "ScriptVersionCache should not be set when reloading from disk"); + this.reload(this.getFileText()); + this.ownFileText = true; } return this.text; } private getLineMap() { - this.ensureNoScriptVersionCache(); + Debug.assert(!this.svc, "ScriptVersionCache should not be set"); return this.lineMap || (this.lineMap = computeLineStarts(this.getOrLoadText())); } - - private setText(newText: string) { - this.ensureNoScriptVersionCache(); - if (newText === undefined || this.text !== newText) { - this.text = newText; - this.lineMap = undefined; - this.textVersion++; - } - } } - export class ScriptInfo { /** * All projects that include this file @@ -150,8 +184,6 @@ namespace ts.server { fileWatcher: FileWatcher; private textStorage: TextStorage; - private isOpen: boolean; - constructor( private readonly host: ServerHost, readonly fileName: NormalizedPath, @@ -169,19 +201,28 @@ namespace ts.server { } public isScriptOpen() { - return this.isOpen; + return this.textStorage.isOpen; } public open(newText: string) { - this.isOpen = true; - this.textStorage.useScriptVersionCache(newText); - this.markContainingProjectsAsDirty(); + this.textStorage.isOpen = true; + if (newText !== undefined && + this.textStorage.reload(newText)) { + // reload new contents only if the existing contents changed + this.markContainingProjectsAsDirty(); + } } public close() { - this.isOpen = false; - this.textStorage.useText(this.hasMixedContent ? "" : undefined); - this.markContainingProjectsAsDirty(); + this.textStorage.isOpen = false; + if (this.hasMixedContent) { + if (this.textStorage.reload("")) { + this.markContainingProjectsAsDirty(); + } + } + else if (this.textStorage.reloadFromDisk()) { + this.markContainingProjectsAsDirty(); + } } public getSnapshot() { @@ -293,26 +334,31 @@ namespace ts.server { return this.textStorage.getVersion(); } - reload(script: string) { - this.textStorage.reload(script); - this.markContainingProjectsAsDirty(); - } - saveTo(fileName: string) { const snap = this.textStorage.getSnapshot(); this.host.writeFile(fileName, snap.getText(0, snap.getLength())); } + /*@internal*/ + delayReloadNonMixedContentFile() { + Debug.assert(!this.hasMixedContent); + this.textStorage.delayReloadFromFileIntoText(); + this.markContainingProjectsAsDirty(); + } + reloadFromFile(tempFileName?: NormalizedPath) { if (this.hasMixedContent) { - this.reload(""); + this.textStorage.reload(""); + this.markContainingProjectsAsDirty(); } else { - this.textStorage.reloadFromFile(tempFileName); - this.markContainingProjectsAsDirty(); + if (this.textStorage.reloadFromFile(tempFileName)) { + this.markContainingProjectsAsDirty(); + } } } + /*@internal*/ getLineInfo(line: number): AbsolutePositionAndLineText { return this.textStorage.getLineInfo(line); } diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index 451536a0caa..7c25e4cc3cb 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -2,6 +2,7 @@ /// /// +/*@internal*/ namespace ts.server { const lineCollectionCapacity = 4; @@ -285,24 +286,6 @@ namespace ts.server { } } - // reload whole script, leaving no change history behind reload - reload(script: string) { - this.currentVersion++; - this.changes = []; // history wiped out by reload - const snap = new LineIndexSnapshot(this.currentVersion, this, new LineIndex()); - - // delete all versions - for (let i = 0; i < this.versions.length; i++) { - this.versions[i] = undefined; - } - - this.versions[this.currentVersionToIndex()] = snap; - const lm = LineIndex.linesFromText(script); - snap.index.load(lm.lines); - - this.minVersion = this.currentVersion; - } - getSnapshot(): IScriptSnapshot { return this._getSnapshot(); } private _getSnapshot(): LineIndexSnapshot { @@ -843,4 +826,4 @@ namespace ts.server { return 1; } } -} \ No newline at end of file +} From 254e39306f771430539fbe94dfe8f11c30e024cd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 28 Aug 2017 12:34:58 -0700 Subject: [PATCH 080/144] Watch failed lookups recursively to reduce number of directory watches Also we dont need to watch type roots any more --- src/compiler/core.ts | 2 + src/compiler/resolutionCache.ts | 87 +++++++++++++++++-- src/compiler/watchedProgram.ts | 9 +- .../unittests/tsserverProjectSystem.ts | 71 ++++----------- src/harness/virtualFileSystemWithWatch.ts | 2 +- src/server/editorServices.ts | 21 ----- src/server/project.ts | 38 +++----- 7 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bb4cf903a98..9c761b47fa0 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1809,6 +1809,8 @@ namespace ts { * Removes a trailing directory separator from a path. * @param path The path. */ + export function removeTrailingDirectorySeparator(path: Path): Path; + export function removeTrailingDirectorySeparator(path: string): string; export function removeTrailingDirectorySeparator(path: string) { if (path.charAt(path.length - 1) === directorySeparator) { return path.substr(0, path.length - 1); diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index d1bd285019f..ca82a68f489 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,6 +14,8 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; + setRootDirectory(dir: string): void; + clear(): void; } @@ -32,13 +34,15 @@ namespace ts { export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; getCompilationSettings(): CompilerOptions; - watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback): FileWatcher; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onInvalidatedResolution(): void; getCachedPartialSystem?(): CachedPartialSystem; projectName?: string; getGlobalCache?(): string | undefined; } + const MAX_DIRPATHS_TO_RECURSE = 5; + export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; @@ -50,6 +54,9 @@ namespace ts { const resolvedTypeReferenceDirectives = createMap>(); const directoryWatchesOfFailedLookups = createMap(); + const failedLookupLocationToDirPath = createMap(); + let rootDir: string; + let rootPath: Path; return { startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, @@ -57,12 +64,27 @@ namespace ts { resolveTypeReferenceDirectives, invalidateResolutionOfFile, createHasInvalidatedResolution, + setRootDirectory, clear }; + function setRootDirectory(dir: string) { + Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size); + rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, resolutionHost.getCurrentDirectory())); + rootPath = resolutionHost.toPath(rootDir); + } + + function isInDirectoryPath(dir: Path, file: Path) { + if (dir === undefined || file.length <= dir.length) { + return false; + } + return startsWith(file, dir) && file[dir.length] === directorySeparator; + } + function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); + failedLookupLocationToDirPath.clear(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -196,16 +218,58 @@ namespace ts { } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - const dirPath = getDirectoryPath(failedLookupLocationPath); + const cachedDir = failedLookupLocationToDirPath.get(failedLookupLocationPath); + if (cachedDir) { + watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocation, failedLookupLocationPath, /*dir*/ undefined); + return; + } + + if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { + // Watch in directory of rootPath + watchFailedLookupLocationInDirectory(rootPath, failedLookupLocation, failedLookupLocationPath, rootDir); + return; + } + + let dirPath = getDirectoryPath(failedLookupLocationPath); + let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, resolutionHost.getCurrentDirectory())); + for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) { + const parentPath = getDirectoryPath(dirPath); + if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) { + watchFailedLookupLocationInDirectory(dirPath, failedLookupLocation, failedLookupLocationPath, dir); + return; + } + dirPath = parentPath; + dir = getDirectoryPath(dir); + } + + // Verify there are no watches in parent directory + const ancestorDirPath = getAncestorDirectoryWithWatches(dirPath); + // We wont need directory if we are using ancestor since its already cached + watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocation, failedLookupLocationPath, dir); + } + + function getAncestorDirectoryWithWatches(dirPath: Path) { + for (let parentDirPath = getDirectoryPath(dirPath); parentDirPath !== dirPath; parentDirPath = getDirectoryPath(parentDirPath)) { + if (directoryWatchesOfFailedLookups.has(parentDirPath)) { + return parentDirPath; + } + dirPath = parentDirPath; + } + return undefined; + } + + function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocation: string, failedLookupLocationPath: Path, dir: string | undefined) { + failedLookupLocationToDirPath.set(failedLookupLocationPath, dirPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); if (watches) { watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation); } else { + Debug.assert(dir !== undefined); const mapLocations = createMultiMap(); mapLocations.add(failedLookupLocationPath, failedLookupLocation); directoryWatchesOfFailedLookups.set(dirPath, { - watcher: createDirectoryWatcher(getDirectoryPath(failedLookupLocation), dirPath), + watcher: createDirectoryWatcher(dir, dirPath), mapLocations }); } @@ -224,16 +288,22 @@ namespace ts { onAddOrRemoveDirectoryOfFailedLookup(dirPath); resolutionHost.onInvalidatedResolution(); } - else if (onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + else if (onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath, fileOrFolderPath)) { resolutionHost.onInvalidatedResolution(); } - }); + }, WatchDirectoryFlags.Recursive); } function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { - const dirPath = getDirectoryPath(failedLookupLocationPath); + const dirPath = failedLookupLocationToDirPath.get(failedLookupLocationPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation); + // If this was last failed lookup location being tracked by the dir watcher, + // remove the failed lookup location path to dir Path entry + if (!watches.mapLocations.has(failedLookupLocationPath)) { + failedLookupLocationToDirPath.delete(failedLookupLocationPath); + } + // If there are no more files that need this watcher alive, close the watcher if (watches.mapLocations.size === 0) { watches.watcher.close(); directoryWatchesOfFailedLookups.delete(dirPath); @@ -311,8 +381,7 @@ namespace ts { invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); } - function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path) { - const dirPath = getDirectoryPath(fileOrFolder); + function onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath: Path, fileOrFolder: Path) { const watches = directoryWatchesOfFailedLookups.get(dirPath); const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); if (isFailedLookupFile) { @@ -324,7 +393,7 @@ namespace ts { } function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) { - const isInDirPath: (location: string) => boolean = location => getDirectoryPath(resolutionHost.toPath(location)) === dirPath; + const isInDirPath: (location: string) => boolean = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 9058588ade1..07a783c3c47 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -297,7 +297,10 @@ namespace ts { }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost); - + resolutionCache.setRootDirectory(configFileName ? + getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : + getCurrentDirectory() + ); // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); @@ -576,8 +579,8 @@ namespace ts { } } - function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { - return watchDirectory(system, directory, cb, WatchDirectoryFlags.None, writeLog); + function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { + return watchDirectory(system, directory, cb, flags, writeLog); } function watchMissingFilePath(missingFilePath: Path) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 9068afa98cf..f5cd1766c39 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -321,34 +321,6 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } - function getPathsForTypesOrModules(base: string, rootPaths: string[], typesOrModules: string[], map: Map, rootDir?: string) { - while (1) { - forEach(rootPaths, r => { - const rp = combinePaths(base, r); - forEach(typesOrModules, tm => { - map.set(tm === "" ? rp : combinePaths(rp, tm), true); - }); - }); - const parentDir = getDirectoryPath(base); - if (base === rootDir || parentDir === base) { - break; - } - base = parentDir; - } - return map; - } - - function getNodeModulesWatchedDirectories(path: string, modules: string[], map = createMap()) { - forEach(modules, module => { - getPathsForTypesOrModules(path, ["node_modules"], ["", module, "@types", `@types/${module}`], map); - }); - return map; - } - - function getTypesWatchedDirectories(path: string, typeRoots: string[], types: string[], map = createMap()) { - return getPathsForTypesOrModules(path, typeRoots, types.concat(""), map, path); - } - describe("tsserverProjectSystem", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -386,8 +358,8 @@ namespace ts.projectSystem { const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); - checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false); - checkWatchedDirectories(host, [], /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, ["/"], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -4040,7 +4012,7 @@ namespace ts.projectSystem { const calledMaps = getCallsTrackingMap(); return { verifyNoCall, - verifyCalledOnEachEntryOnce, + verifyCalledOnEachEntryNTimes, verifyCalledOnEachEntry, verifyNoHostCalls, verifyNoHostCallsExceptFileExistsOnce, @@ -4090,8 +4062,8 @@ namespace ts.projectSystem { }); } - function verifyCalledOnEachEntryOnce(callback: keyof CalledMaps, expectedKeys: string[]) { - return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => 1))); + function verifyCalledOnEachEntryNTimes(callback: keyof CalledMaps, expectedKeys: string[], nTimes: number) { + return verifyCalledOnEachEntry(callback, zipToMap(expectedKeys, expectedKeys.map(() => nTimes))); } function verifyNoHostCalls() { @@ -4101,7 +4073,7 @@ namespace ts.projectSystem { } function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) { - verifyCalledOnEachEntryOnce("fileExists", expectedKeys); + verifyCalledOnEachEntryNTimes("fileExists", expectedKeys, 1); verifyNoCall("directoryExists"); verifyNoCall("getDirectories"); verifyNoCall("readFile"); @@ -4253,17 +4225,7 @@ namespace ts.projectSystem { const { configFileName } = projectService.openClientFile(file1.path); assert.equal(configFileName, tsconfigFile.path, `should find config`); checkNumberOfConfiguredProjects(projectService, 1); - const watchedModuleDirectories = arrayFrom( - getNodeModulesWatchedDirectories( - canonicalFrontendDir, - types, - getTypesWatchedDirectories( - canonicalFrontendDir, - typeRoots, - types - ) - ).keys() - ); + const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, canonicalFrontendDir, "/"]; const project = projectService.configuredProjects.get(canonicalConfigPath); verifyProjectAndWatchedDirectories(); @@ -4276,7 +4238,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - callsTrackingHost.verifyCalledOnEachEntryOnce("fileExists", [canonicalFile3Path]); + callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", [canonicalFile3Path], watchingRecursiveDirectories.length); // Called for type root resolution const directoryExistsCalled = createMap(); @@ -4286,11 +4248,11 @@ namespace ts.projectSystem { directoryExistsCalled.set(`/node_modules`, 2); directoryExistsCalled.set(`${frontendDir}/types`, 2); directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); - directoryExistsCalled.set(canonicalFile3Path, 1); + directoryExistsCalled.set(canonicalFile3Path, watchingRecursiveDirectories.length); callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); callsTrackingHost.verifyNoCall("getDirectories"); - callsTrackingHost.verifyCalledOnEachEntryOnce("readFile", [file3.path]); + callsTrackingHost.verifyCalledOnEachEntryNTimes("readFile", [file3.path], 1); callsTrackingHost.verifyNoCall("readDirectory"); checkNumberOfConfiguredProjects(projectService, 1); @@ -4316,8 +4278,8 @@ namespace ts.projectSystem { function verifyProjectAndWatchedDirectories() { checkProjectActualFiles(project, map(projectFiles, f => f.path)); checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen)); - checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); - checkWatchedDirectories(host, watchedModuleDirectories, /*recursive*/ false); + checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); } } @@ -4372,7 +4334,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName } = projectService.openClientFile(app.path); assert.equal(configFileName, tsconfigJson.path, `should find config`); - const watchedModuleLocations = arrayFrom(getNodeModulesWatchedDirectories(appFolder, ["lodash"]).keys()); + const recursiveWatchedDirectories: string[] = [appFolder, "/"]; verifyProject(); let timeoutAfterReloadFs = timeoutDuringPartialInstallation; @@ -4450,7 +4412,8 @@ namespace ts.projectSystem { const lodashIndexPath = "/a/b/node_modules/@types/lodash/index.d.ts"; projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)); - watchedModuleLocations.length = indexOf(watchedModuleLocations, getDirectoryPath(lodashIndexPath)); + // we would now not have failed lookup in the parent of appFolder since lodash is available + recursiveWatchedDirectories.length = 1; // npm installation complete, timeout after reload fs timeoutAfterReloadFs = true; verifyAfterPartialOrCompleteNpmInstall(2); @@ -4475,8 +4438,8 @@ namespace ts.projectSystem { const filesWatched = filter(projectFilePaths, p => p !== app.path); checkWatchedFiles(host, filesWatched); - checkWatchedDirectories(host, [appFolder], /*recursive*/ true); - checkWatchedDirectories(host, watchedModuleLocations, /*recursive*/ false); + checkWatchedDirectories(host, recursiveWatchedDirectories, /*recursive*/ true); + checkWatchedDirectories(host, [], /*recursive*/ false); } } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 4b249c78e15..724326d3eb6 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -141,7 +141,7 @@ namespace ts.TestFSWithWatch { } export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) { - checkMapKeys("watchedDirectories", recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); + checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories); } export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9195083bb68..e6976008c83 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -244,7 +244,6 @@ namespace ts.server { 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", ConfigFileForInferredRoot = "Config file for the inferred project root", FailedLookupLocation = "Directory of Failed lookup locations in module resolution" @@ -711,23 +710,6 @@ namespace ts.server { } } - /*@internal*/ - watchTypeRootDirectory(root: Path, project: ConfiguredProject) { - // TODO: This is not needed anymore with watches for failed lookup locations? - return this.watchDirectory( - this.host, - root, - fileOrFolder => { - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); - project.updateTypes(); - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); - }, - WatchDirectoryFlags.None, - WatchType.TypeRoot, - project - ); - } - /** * This is to watch whenever files are added or removed to the wildcard directories */ @@ -1434,7 +1416,6 @@ namespace ts.server { ); if (languageServiceEnabled) { project.watchWildcards(projectOptions.wildcardDirectories); - project.watchTypeRoots(); } project.setProjectErrors(configFileErrors); @@ -1534,12 +1515,10 @@ namespace ts.server { if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { project.disableLanguageService(); project.stopWatchingWildCards(); - project.stopWatchingTypeRoots(); } else { project.enableLanguageService(); project.watchWildcards(projectOptions.wildcardDirectories); - project.watchTypeRoots(); } this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave); diff --git a/src/server/project.ts b/src/server/project.ts index 1f6b762b674..8d391c312b1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -351,12 +351,12 @@ namespace ts.server { } /*@internal*/ - watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { return this.projectService.watchDirectory( this.projectService.host, directory, cb, - WatchDirectoryFlags.None, + flags, WatchType.FailedLookupLocation, this ); @@ -1012,6 +1012,13 @@ namespace ts.server { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } super.addRoot(info); + // For first root set the resolution cache root dir + if (this.getRootFiles.length === 1) { + const rootDirPath = this.getProjectRootPath(); + if (rootDirPath) { + this.resolutionCache.setRootDirectory(rootDirPath); + } + } } removeRoot(info: ScriptInfo) { @@ -1064,7 +1071,6 @@ namespace ts.server { /* @internal */ configFileWatcher: FileWatcher; private directoriesWatchedForWildcards: Map | undefined; - private typeRootsWatchers: Map | undefined; readonly canonicalConfigFilePath: NormalizedPath; /* @internal */ @@ -1091,6 +1097,7 @@ namespace ts.server { super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedPartialSystem); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); + this.resolutionCache.setRootDirectory(getDirectoryPath(configFileName)); } /** @@ -1252,29 +1259,6 @@ namespace ts.server { } } - /*@internal*/ - watchTypeRoots() { - const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir)); - mutateMap( - this.typeRootsWatchers || (this.typeRootsWatchers = createMap()), - newTypeRoots, - { - // Create new watch - createNewValue: root => this.projectService.watchTypeRootDirectory(root as Path, this), - // Close existing watch thats not needed any more - onDeleteValue: closeFileWatcher - } - ); - } - - /*@internal*/ - stopWatchingTypeRoots() { - if (this.typeRootsWatchers) { - clearMap(this.typeRootsWatchers, closeFileWatcher); - this.typeRootsWatchers = undefined; - } - } - close() { super.close(); @@ -1283,7 +1267,6 @@ namespace ts.server { this.configFileWatcher = undefined; } - this.stopWatchingTypeRoots(); this.stopWatchingWildCards(); } @@ -1325,6 +1308,7 @@ namespace ts.server { public compileOnSaveEnabled: boolean, private readonly projectFilePath?: string) { super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host); + this.resolutionCache.setRootDirectory(this.getProjectRootPath()); } getProjectRootPath() { From a3b9467d4159ac0d3ff833149ad1517e249598c8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 28 Aug 2017 17:09:07 -0700 Subject: [PATCH 081/144] Resolve only once in the given directory for name --- src/compiler/resolutionCache.ts | 104 +++++++++++++++++++++++++------- src/compiler/watchedProgram.ts | 5 +- src/server/project.ts | 12 ++++ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index ca82a68f489..7dff3f55155 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,6 +14,9 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; + startCachingPerDirectoryResolution(): void; + finishCachingPerDirectoryResolution(): void; + setRootDirectory(dir: string): void; clear(): void; @@ -28,7 +31,7 @@ namespace ts { /** watcher for the directory of failed lookup */ watcher: FileWatcher; /** map with key being the failed lookup location path and value being the actual location */ - mapLocations: MultiMap; + mapLocations: Map; } export interface ResolutionCacheHost extends ModuleResolutionHost { @@ -39,6 +42,7 @@ namespace ts { getCachedPartialSystem?(): CachedPartialSystem; projectName?: string; getGlobalCache?(): string | undefined; + writeLog(s: string): void; } const MAX_DIRPATHS_TO_RECURSE = 5; @@ -51,7 +55,9 @@ namespace ts { // The key in the map is source file's path. // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); + const perDirectoryResolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); + const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const directoryWatchesOfFailedLookups = createMap(); const failedLookupLocationToDirPath = createMap(); @@ -60,6 +66,8 @@ namespace ts { return { startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, + startCachingPerDirectoryResolution, + finishCachingPerDirectoryResolution, resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, @@ -87,6 +95,7 @@ namespace ts { failedLookupLocationToDirPath.clear(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); + Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); } function startRecordingFilesWithChangedResolutions() { @@ -105,6 +114,15 @@ namespace ts { return path => collected && collected.has(path); } + function startCachingPerDirectoryResolution() { + Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); + } + + function finishCachingPerDirectoryResolution() { + perDirectoryResolvedModuleNames.clear(); + perDirectoryResolvedTypeReferenceDirectives.clear(); + } + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts @@ -131,6 +149,7 @@ namespace ts { names: string[], containingFile: string, cache: Map>, + perDirectoryCache: Map>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, getResult: (s: T) => R, getResultFileName: (result: R) => string | undefined, @@ -138,6 +157,12 @@ namespace ts { const path = resolutionHost.toPath(containingFile); const currentResolutionsInFile = cache.get(path); + const dirPath = getDirectoryPath(path); + let perDirectoryResolution = perDirectoryCache.get(dirPath); + if (!perDirectoryResolution) { + perDirectoryResolution = createMap(); + perDirectoryCache.set(dirPath, perDirectoryResolution); + } const newResolutions: Map = createMap(); const resolvedModules: R[] = []; @@ -148,12 +173,24 @@ namespace ts { let resolution = newResolutions.get(name); if (!resolution) { const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); + if (existingResolution) { + // Remove from the cache since we would update the resolution in new file ourselves + currentResolutionsInFile.delete(name); + } + if (moduleResolutionIsValid(existingResolution)) { // ok, it is safe to use existing name resolution results resolution = existingResolution; } else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost); + const resolutionInDirectory = perDirectoryResolution && perDirectoryResolution.get(name); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; + } + else { + resolution = loader(name, containingFile, compilerOptions, resolutionHost); + perDirectoryResolution.set(name, resolution); + } updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); @@ -169,6 +206,11 @@ namespace ts { resolvedModules.push(getResult(resolution)); } + // Close all the file watchers for the names that arent required any more + if (currentResolutionsInFile) { + clearMap(currentResolutionsInFile, resolution => withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher)); + } + // replace old results with a new one cache.set(path, newResolutions); return resolvedModules; @@ -208,25 +250,35 @@ namespace ts { } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return resolveNamesWithLocalCache(typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, - m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, /*logChanges*/ false); + resolutionHost.writeLog(`resolveTypeReferenceDirectives: ${typeDirectiveNames} in ${containingFile}`); + return resolveNamesWithLocalCache( + typeDirectiveNames, containingFile, + resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, + resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, + /*logChanges*/ false + ); } function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { - return resolveNamesWithLocalCache(moduleNames, containingFile, resolvedModuleNames, resolveModuleName, - m => m.resolvedModule, r => r.resolvedFileName, logChanges); + resolutionHost.writeLog(`resolveModuleNames: ${moduleNames} in ${containingFile}`); + return resolveNamesWithLocalCache( + moduleNames, containingFile, + resolvedModuleNames, perDirectoryResolvedModuleNames, + resolveModuleName, m => m.resolvedModule, r => r.resolvedFileName, + logChanges + ); } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { const cachedDir = failedLookupLocationToDirPath.get(failedLookupLocationPath); if (cachedDir) { - watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocation, failedLookupLocationPath, /*dir*/ undefined); + watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocationPath, /*dir*/ undefined); return; } if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { // Watch in directory of rootPath - watchFailedLookupLocationInDirectory(rootPath, failedLookupLocation, failedLookupLocationPath, rootDir); + watchFailedLookupLocationInDirectory(rootPath, failedLookupLocationPath, rootDir); return; } @@ -235,7 +287,7 @@ namespace ts { for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) { const parentPath = getDirectoryPath(dirPath); if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) { - watchFailedLookupLocationInDirectory(dirPath, failedLookupLocation, failedLookupLocationPath, dir); + watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir); return; } dirPath = parentPath; @@ -245,7 +297,7 @@ namespace ts { // Verify there are no watches in parent directory const ancestorDirPath = getAncestorDirectoryWithWatches(dirPath); // We wont need directory if we are using ancestor since its already cached - watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocation, failedLookupLocationPath, dir); + watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocationPath, dir); } function getAncestorDirectoryWithWatches(dirPath: Path) { @@ -258,16 +310,17 @@ namespace ts { return undefined; } - function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocation: string, failedLookupLocationPath: Path, dir: string | undefined) { + function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocationPath: Path, dir: string | undefined) { failedLookupLocationToDirPath.set(failedLookupLocationPath, dirPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); if (watches) { - watches.mapLocations.add(failedLookupLocationPath, failedLookupLocation); + const existingCount = watches.mapLocations.get(failedLookupLocationPath) || 0; + watches.mapLocations.set(failedLookupLocationPath, existingCount + 1); } else { Debug.assert(dir !== undefined); - const mapLocations = createMultiMap(); - mapLocations.add(failedLookupLocationPath, failedLookupLocation); + const mapLocations = createMap(); + mapLocations.set(failedLookupLocationPath, 1); directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), mapLocations @@ -294,19 +347,24 @@ namespace ts { }, WatchDirectoryFlags.Recursive); } - function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { + function closeFailedLookupLocationWatcher(_failedLookupLocation: string, failedLookupLocationPath: Path) { const dirPath = failedLookupLocationToDirPath.get(failedLookupLocationPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); - watches.mapLocations.remove(failedLookupLocationPath, failedLookupLocation); - // If this was last failed lookup location being tracked by the dir watcher, - // remove the failed lookup location path to dir Path entry - if (!watches.mapLocations.has(failedLookupLocationPath)) { + const refCount = watches.mapLocations.get(failedLookupLocationPath); + if (refCount === 1) { + // If this was last failed lookup location being tracked by the dir watcher, + // remove the failed lookup location path to dir Path entry + watches.mapLocations.delete(failedLookupLocationPath); failedLookupLocationToDirPath.delete(failedLookupLocationPath); + + // If there are no more files that need this watcher alive, close the watcher + if (watches.mapLocations.size === 0) { + watches.watcher.close(); + directoryWatchesOfFailedLookups.delete(dirPath); + } } - // If there are no more files that need this watcher alive, close the watcher - if (watches.mapLocations.size === 0) { - watches.watcher.close(); - directoryWatchesOfFailedLookups.delete(dirPath); + else { + watches.mapLocations.set(failedLookupLocationPath, refCount - 1); } } diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 07a783c3c47..dbac0861aa5 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -293,7 +293,8 @@ namespace ts { getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation, getCachedPartialSystem, - onInvalidatedResolution: scheduleProgramUpdate + onInvalidatedResolution: scheduleProgramUpdate, + writeLog }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost); @@ -330,8 +331,10 @@ namespace ts { beforeCompile(compilerOptions); // Compile the program + resolutionCache.startCachingPerDirectoryResolution(); compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); + resolutionCache.finishCachingPerDirectoryResolution(); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); // Update watches diff --git a/src/server/project.ts b/src/server/project.ts index 8d391c312b1..78987d398d4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -372,6 +372,11 @@ namespace ts.server { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } + /*@internal*/ + writeLog(s: string) { + this.projectService.logger.info(s); + } + private setInternalCompilerOptionsForEmittingJsFiles() { if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) { this.compilerOptions.noEmitForJsFiles = true; @@ -767,7 +772,10 @@ namespace ts.server { private updateGraphWorker() { const oldProgram = this.program; + + this.resolutionCache.startCachingPerDirectoryResolution(); this.program = this.languageService.getProgram(); + this.resolutionCache.finishCachingPerDirectoryResolution(); // bump up the version if // - oldProgram is not set - this is a first time updateGraph is called @@ -795,6 +803,10 @@ namespace ts.server { // Watch the missing files missingFilePath => this.addMissingFileWatcher(missingFilePath) ); + + // Update typeRoots watch + // Watch the type locations that would be added to program as part of automatic type resolutions + // TODO } const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray; From 16cf7c40a8c1367827d82214233644e6c1191a17 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 30 Aug 2017 11:49:58 -0700 Subject: [PATCH 082/144] Watch for the automatic types that included as part of type resolution --- src/compiler/core.ts | 3 + src/compiler/program.ts | 20 ++++-- src/compiler/resolutionCache.ts | 68 ++++++++++++++++++- src/compiler/types.ts | 1 + src/compiler/watchedProgram.ts | 32 ++++++--- src/harness/unittests/tscWatchMode.ts | 6 +- .../unittests/tsserverProjectSystem.ts | 30 ++++++-- src/server/editorServices.ts | 3 +- src/server/project.ts | 45 ++++++++---- src/services/services.ts | 9 ++- src/services/types.ts | 1 + 11 files changed, 179 insertions(+), 39 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9c761b47fa0..4f40a5d1ab3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1224,6 +1224,9 @@ namespace ts { /** Do nothing and return false */ export function returnFalse(): false { return false; } + /** Do nothing and return true */ + export function returnTrue(): true { return true; } + /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 22459d36cb0..f74675dbf31 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -393,10 +393,17 @@ namespace ts { allDiagnostics?: Diagnostic[]; } - export function isProgramUptoDate(program: Program | undefined, rootFileNames: string[], newOptions: CompilerOptions, - getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean { - // If we haven't create a program yet, then it is not up-to-date - if (!program) { + export function isProgramUptoDate( + program: Program | undefined, + rootFileNames: string[], + newOptions: CompilerOptions, + getSourceVersion: (path: Path) => string, + fileExists: (fileName: string) => boolean, + hasInvalidatedResolution: HasInvalidatedResolution, + hasChangedAutomaticTypeDirectiveNames: () => boolean, + ): boolean { + // If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date + if (!program || hasChangedAutomaticTypeDirectiveNames()) { return false; } @@ -587,6 +594,7 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; + const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. @@ -1090,6 +1098,10 @@ namespace ts { return oldProgram.structureIsReused; } + if (hasChangedAutomaticTypeDirectiveNames()) { + return oldProgram.structureIsReused = StructureIsReused.SafeModules; + } + missingFilePaths = oldProgram.getMissingFilePaths(); // update fileName -> file mapping diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 7dff3f55155..37272cac69d 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -19,6 +19,9 @@ namespace ts { setRootDirectory(dir: string): void; + updateTypeRootsWatch(): void; + closeTypeRootsWatch(): void; + clear(): void; } @@ -39,6 +42,8 @@ namespace ts { getCompilationSettings(): CompilerOptions; watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onInvalidatedResolution(): void; + watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; + onChangedAutomaticTypeDirectiveNames(): void; getCachedPartialSystem?(): CachedPartialSystem; projectName?: string; getGlobalCache?(): string | undefined; @@ -59,10 +64,16 @@ namespace ts { const resolvedTypeReferenceDirectives = createMap>(); const perDirectoryResolvedTypeReferenceDirectives = createMap>(); + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const directoryWatchesOfFailedLookups = createMap(); const failedLookupLocationToDirPath = createMap(); let rootDir: string; let rootPath: Path; + + // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames + const typeRootsWatches = createMap(); + return { startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, @@ -73,12 +84,14 @@ namespace ts { invalidateResolutionOfFile, createHasInvalidatedResolution, setRootDirectory, + updateTypeRootsWatch, + closeTypeRootsWatch, clear }; function setRootDirectory(dir: string) { Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size); - rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, resolutionHost.getCurrentDirectory())); + rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory())); rootPath = resolutionHost.toPath(rootDir); } @@ -93,6 +106,7 @@ namespace ts { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); failedLookupLocationToDirPath.clear(); + closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); @@ -283,7 +297,7 @@ namespace ts { } let dirPath = getDirectoryPath(failedLookupLocationPath); - let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, resolutionHost.getCurrentDirectory())); + let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())); for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) { const parentPath = getDirectoryPath(dirPath); if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) { @@ -455,5 +469,55 @@ namespace ts { invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); } + + function closeTypeRootsWatch() { + clearMap(typeRootsWatches, closeFileWatcher); + } + + function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher { + // Create new watch and recursive info + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => { + const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + if (resolutionHost.getCachedPartialSystem) { + // Since the file existance changed, update the sourceFiles cache + resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // For now just recompile + // We could potentially store more data here about whether it was/would be really be used or not + // and with that determine to trigger compilation but for now this is enough + resolutionHost.onChangedAutomaticTypeDirectiveNames(); + }, WatchDirectoryFlags.Recursive); + } + + /** + * Watches the types that would get added as part of getAutomaticTypeDirectiveNames + * To be called when compiler options change + */ + function updateTypeRootsWatch() { + const options = resolutionHost.getCompilationSettings(); + if (options.types) { + // No need to do any watch since resolution cache is going to handle the failed lookups + // for the types added by this + closeTypeRootsWatch(); + return; + } + + // we need to assume the directories exist to ensure that we can get all the type root directories that get included + const typeRoots = getEffectiveTypeRoots(options, { directoryExists: returnTrue, getCurrentDirectory }); + if (typeRoots) { + mutateMap( + typeRootsWatches, + arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), + { + createNewValue: createTypeRootsWatch, + onDeleteValue: closeFileWatcher + } + ); + } + else { + closeTypeRootsWatch(); + } + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 026a8bf8631..a105e5fcbfe 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4067,6 +4067,7 @@ namespace ts { getEnvironmentVariable?(name: string): string; onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; hasInvalidatedResolution?: HasInvalidatedResolution; + hasChangedAutomaticTypeDirectiveNames?(): boolean; } /* @internal */ diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index dbac0861aa5..0dba848eb1c 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -246,12 +246,13 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations + let changedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop; const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher; const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; - const watchDirectory = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; + const watchDirectoryWorker = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; @@ -288,12 +289,15 @@ namespace ts { resolveTypeReferenceDirectives, resolveModuleNames, onReleaseOldSourceFile, + hasChangedAutomaticTypeDirectiveNames, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, - watchDirectoryOfFailedLookupLocation, + watchDirectoryOfFailedLookupLocation: watchDirectory, + watchTypeRootsDirectory: watchDirectory, getCachedPartialSystem, onInvalidatedResolution: scheduleProgramUpdate, + onChangedAutomaticTypeDirectiveNames, writeLog }; // Cache for the module resolution @@ -320,13 +324,14 @@ namespace ts { } const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); - if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) { + if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { return; } if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } + const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; hasChangedCompilerOptions = false; beforeCompile(compilerOptions); @@ -339,6 +344,10 @@ namespace ts { // Update watches updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); + if (needsUpdateInTypeRootWatch) { + resolutionCache.updateTypeRootsWatch(); + } + if (missingFilePathsRequestedForRelease) { // These are the paths that program creater told us as not in use any more but were missing on the disk. // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, @@ -582,8 +591,17 @@ namespace ts { } } - function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { - return watchDirectory(system, directory, cb, flags, writeLog); + function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { + return watchDirectoryWorker(system, directory, cb, flags, writeLog); + } + + function onChangedAutomaticTypeDirectiveNames() { + changedAutomaticTypeDirectiveNames = true; + scheduleProgramUpdate(); + } + + function hasChangedAutomaticTypeDirectiveNames() { + return changedAutomaticTypeDirectiveNames; } function watchMissingFilePath(missingFilePath: Path) { @@ -615,7 +633,6 @@ namespace ts { function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { return watchDirectory( - system, directory, fileOrFolder => { Debug.assert(!!configFileName); @@ -645,8 +662,7 @@ namespace ts { scheduleProgramUpdate(); } }, - flags, - writeLog + flags ); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index f5ed08b9b87..66e58a502d6 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -167,7 +167,8 @@ namespace ts.tscWatch { checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]); checkProgramRootFiles(watch(), [file1.path, file2.path]); checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); - checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); + const configDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true); }); // TODO: if watching for config file creation @@ -181,7 +182,8 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([commonFile1, libFile, configFile]); const watch = createWatchModeWithConfigFile(configFile.path, host); - checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); + const configDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true); checkProgramRootFiles(watch(), [commonFile1.path]); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index f5cd1766c39..22623ad1c8b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -321,6 +321,22 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } + const typeRootFromTsserverLocation = "/node_modules/@types"; + + export function getTypeRootsFromLocation(currentDirectory: string) { + currentDirectory = normalizePath(currentDirectory); + const result: string[] = []; + while (true) { + result.push(combinePaths(currentDirectory, "node_modules/@types")); + const parentDirectory = getDirectoryPath(currentDirectory); + if (parentDirectory === currentDirectory) { + break; + } + currentDirectory = parentDirectory; + } + return result; + } + describe("tsserverProjectSystem", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -359,7 +375,7 @@ namespace ts.projectSystem { const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, ["/"], /*recursive*/ true); + checkWatchedDirectories(host, ["/", typeRootFromTsserverLocation], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -430,7 +446,8 @@ namespace ts.projectSystem { checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true); + const configFileDirectory = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, getTypeRootsFromLocation(configFileDirectory).concat(configFileDirectory), /*recursive*/ true); }); it("create configured project with the file list", () => { @@ -518,7 +535,8 @@ namespace ts.projectSystem { const host = createServerHost([commonFile1, libFile, configFile]); const projectService = createProjectService(host); projectService.openClientFile(commonFile1.path); - checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true); + const configFileDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, getTypeRootsFromLocation(configFileDir).concat(configFileDir), /*recursive*/ true); checkNumberOfConfiguredProjects(projectService, 1); const project = configuredProjectAt(projectService, 0); @@ -4329,6 +4347,7 @@ namespace ts.projectSystem { }; const appFolder = getDirectoryPath(app.path); const projectFiles = [app, libFile, tsconfigJson]; + const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path)); const otherFiles = [packageJson]; const host = createServerHost(projectFiles.concat(otherFiles)); const projectService = createProjectService(host); @@ -4402,7 +4421,8 @@ namespace ts.projectSystem { { "path": "/a/b/node_modules/typescript" }, { "path": "/a/b/node_modules/.bin" } ); - verifyAfterPartialOrCompleteNpmInstall(0); + // From the type root update + verifyAfterPartialOrCompleteNpmInstall(2); forEach(filesAndFoldersToAdd, f => { f.path = f.path @@ -4438,7 +4458,7 @@ namespace ts.projectSystem { const filesWatched = filter(projectFilePaths, p => p !== app.path); checkWatchedFiles(host, filesWatched); - checkWatchedDirectories(host, recursiveWatchedDirectories, /*recursive*/ true); + checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true); checkWatchedDirectories(host, [], /*recursive*/ false); } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e6976008c83..e6784942c66 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -246,7 +246,8 @@ namespace ts.server { WildcardDirectories = "Wild card directory", ClosedScriptInfo = "Closed Script info", ConfigFileForInferredRoot = "Config file for the inferred project root", - FailedLookupLocation = "Directory of Failed lookup locations in module resolution" + FailedLookupLocation = "Directory of Failed lookup locations in module resolution", + TypeRoots = "Type root directory" } const enum ConfigFileWatcherStatus { diff --git a/src/server/project.ts b/src/server/project.ts index 78987d398d4..7dc236e8edf 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -162,9 +162,9 @@ namespace ts.server { */ private projectStateVersion = 0; - private typingFiles: SortedReadonlyArray; + private changedAutomaticTypeDirectiveNames = false; - private typesVersion = 0; + private typingFiles: SortedReadonlyArray; public isNonTsProject() { this.updateGraph(); @@ -224,10 +224,10 @@ namespace ts.server { } this.languageService = createLanguageService(this, this.documentRegistry); + this.resolutionCache = createResolutionCache(this); if (!languageServiceEnabled) { this.disableLanguageService(); } - this.resolutionCache = createResolutionCache(this); this.markAsDirty(); } @@ -325,10 +325,6 @@ namespace ts.server { return !this.isWatchedMissingFile(path) && this.partialSystem.fileExists(file); } - getTypeRootsVersion() { - return this.typesVersion; - } - resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); } @@ -367,6 +363,29 @@ namespace ts.server { this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } + /*@internal*/ + watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { + return this.projectService.watchDirectory( + this.projectService.host, + directory, + cb, + flags, + WatchType.TypeRoots, + this + ); + } + + /*@internal*/ + onChangedAutomaticTypeDirectiveNames() { + this.changedAutomaticTypeDirectiveNames = true; + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + } + + /*@internal*/ + hasChangedAutomaticTypeDirectiveNames() { + return this.changedAutomaticTypeDirectiveNames; + } + /*@internal*/ getGlobalCache() { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; @@ -458,6 +477,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; + this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -478,10 +498,6 @@ namespace ts.server { return this.program.getSourceFileByPath(path); } - updateTypes() { - this.typesVersion++; - } - close() { if (this.program) { // if we have a program - release all files that are enlisted in program @@ -781,7 +797,7 @@ namespace ts.server { // - oldProgram is not set - this is a first time updateGraph is called // - newProgram is different from the old program and structure of the old program was not reused. const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)); - + this.changedAutomaticTypeDirectiveNames = false; if (hasChanges) { if (oldProgram) { for (const f of oldProgram.getSourceFiles()) { @@ -804,9 +820,10 @@ namespace ts.server { missingFilePath => this.addMissingFileWatcher(missingFilePath) ); - // Update typeRoots watch // Watch the type locations that would be added to program as part of automatic type resolutions - // TODO + if (this.languageServiceEnabled) { + this.resolutionCache.updateTypeRootsWatch(); + } } const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray; diff --git a/src/services/services.ts b/src/services/services.ts index 1939133b6b0..087ffb42515 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1095,11 +1095,13 @@ namespace ts { } function synchronizeHostData(): void { + const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; + // perform fast check if host supports it if (host.getProjectVersion) { const hostProjectVersion = host.getProjectVersion(); if (hostProjectVersion) { - if (lastProjectVersion === hostProjectVersion) { + if (lastProjectVersion === hostProjectVersion && !hasChangedAutomaticTypeDirectiveNames()) { return; } @@ -1121,7 +1123,7 @@ namespace ts { const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution)) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { return; } @@ -1161,7 +1163,8 @@ namespace ts { return host.getDirectories ? host.getDirectories(path) : []; }, onReleaseOldSourceFile, - hasInvalidatedResolution + hasInvalidatedResolution, + hasChangedAutomaticTypeDirectiveNames }; if (host.trace) { compilerHost.trace = message => host.trace(message); diff --git a/src/services/types.ts b/src/services/types.ts index 05715f018bd..b26b015049f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -186,6 +186,7 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; hasInvalidatedResolution?: HasInvalidatedResolution; + hasChangedAutomaticTypeDirectiveNames?(): boolean; directoryExists?(directoryName: string): boolean; /* From d7ce95df254033f2c6e1c6bc959c29a83cfed448 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 30 Aug 2017 17:15:40 -0700 Subject: [PATCH 083/144] Watch node_modules if possible --- src/compiler/resolutionCache.ts | 107 +++++++++++++++++++------------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 37272cac69d..ff34a4b9a07 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -50,8 +50,6 @@ namespace ts { writeLog(s: string): void; } - const MAX_DIRPATHS_TO_RECURSE = 5; - export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; @@ -264,7 +262,6 @@ namespace ts { } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - resolutionHost.writeLog(`resolveTypeReferenceDirectives: ${typeDirectiveNames} in ${containingFile}`); return resolveNamesWithLocalCache( typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, @@ -274,7 +271,6 @@ namespace ts { } function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { - resolutionHost.writeLog(`resolveModuleNames: ${moduleNames} in ${containingFile}`); return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, @@ -284,48 +280,64 @@ namespace ts { } function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - const cachedDir = failedLookupLocationToDirPath.get(failedLookupLocationPath); - if (cachedDir) { - watchFailedLookupLocationInDirectory(cachedDir, failedLookupLocationPath, /*dir*/ undefined); + const failedLocationDirPath = getDirectoryPath(failedLookupLocationPath); + const cachedDirPath = failedLookupLocationToDirPath.get(failedLocationDirPath); + if (cachedDirPath) { + watchFailedLookupLocationInDirectory(cachedDirPath, failedLookupLocationPath, /*dir*/ undefined); return; } if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { // Watch in directory of rootPath + failedLookupLocationToDirPath.set(failedLocationDirPath, rootPath); watchFailedLookupLocationInDirectory(rootPath, failedLookupLocationPath, rootDir); return; } - let dirPath = getDirectoryPath(failedLookupLocationPath); - let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())); - for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) { - const parentPath = getDirectoryPath(dirPath); - if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) { - watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir); - return; - } - dirPath = parentPath; - dir = getDirectoryPath(dir); - } - - // Verify there are no watches in parent directory - const ancestorDirPath = getAncestorDirectoryWithWatches(dirPath); - // We wont need directory if we are using ancestor since its already cached - watchFailedLookupLocationInDirectory(ancestorDirPath || dirPath, failedLookupLocationPath, dir); + const { dir, dirPath } = getDirectoryToWatchWhenNotInRootPath( + getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), + getDirectoryPath(failedLookupLocationPath) + ); + failedLookupLocationToDirPath.set(failedLocationDirPath, dirPath); + watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir); } - function getAncestorDirectoryWithWatches(dirPath: Path) { - for (let parentDirPath = getDirectoryPath(dirPath); parentDirPath !== dirPath; parentDirPath = getDirectoryPath(parentDirPath)) { - if (directoryWatchesOfFailedLookups.has(parentDirPath)) { - return parentDirPath; - } - dirPath = parentDirPath; + function isNodeModulesDirectory(dirPath: Path) { + const nodemodules = "/node_modules"; + return endsWith(dirPath, nodemodules); + } + + function getDirectoryToWatchWhenNotInRootPath(dir: string, dirPath: Path): { dir: string, dirPath: Path } { + // If the directory is node_modules use it to watch + if (isNodeModulesDirectory(dirPath)) { + return { dir, dirPath }; } - return undefined; + + // If directory path contains node module, get the node_modules directory for watching + if (dirPath.indexOf("/node_modules/") !== -1) { + while (!isNodeModulesDirectory(dirPath)) { + dir = getDirectoryPath(dir); + dirPath = getDirectoryPath(dirPath); + } + return { dir, dirPath }; + } + + // Use some ancestor of the root directory + if (rootPath !== undefined) { + while (!isInDirectoryPath(dirPath, rootPath)) { + const parentPath = getDirectoryPath(dirPath); + if (parentPath === dirPath) { + break; + } + dirPath = parentPath; + dir = getDirectoryPath(dir); + } + } + + return { dir, dirPath }; } function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocationPath: Path, dir: string | undefined) { - failedLookupLocationToDirPath.set(failedLookupLocationPath, dirPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); if (watches) { const existingCount = watches.mapLocations.get(failedLookupLocationPath) || 0; @@ -350,31 +362,37 @@ namespace ts { resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); } - // If the location results in update to failed lookup, schedule program update - if (dirPath === fileOrFolderPath) { - onAddOrRemoveDirectoryOfFailedLookup(dirPath); - resolutionHost.onInvalidatedResolution(); - } - else if (onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath, fileOrFolderPath)) { - resolutionHost.onInvalidatedResolution(); + // If the files are added to project root or node_modules directory, always run through the invalidation process + // Otherwise run through invalidation only if adding to the immediate directory + if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { + // If the location results in update to failed lookup, schedule program update + if (dirPath === fileOrFolderPath) { + if (onAddOrRemoveDirectoryOfFailedLookup(dirPath)) { + resolutionHost.onInvalidatedResolution(); + } + } + else if (onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath, fileOrFolderPath)) { + resolutionHost.onInvalidatedResolution(); + } } }, WatchDirectoryFlags.Recursive); } function closeFailedLookupLocationWatcher(_failedLookupLocation: string, failedLookupLocationPath: Path) { - const dirPath = failedLookupLocationToDirPath.get(failedLookupLocationPath); + const failedLookupDirectory = getDirectoryPath(failedLookupLocationPath); + const dirPath = failedLookupLocationToDirPath.get(failedLookupDirectory); const watches = directoryWatchesOfFailedLookups.get(dirPath); const refCount = watches.mapLocations.get(failedLookupLocationPath); if (refCount === 1) { // If this was last failed lookup location being tracked by the dir watcher, // remove the failed lookup location path to dir Path entry watches.mapLocations.delete(failedLookupLocationPath); - failedLookupLocationToDirPath.delete(failedLookupLocationPath); // If there are no more files that need this watcher alive, close the watcher if (watches.mapLocations.size === 0) { watches.watcher.close(); directoryWatchesOfFailedLookups.delete(dirPath); + failedLookupLocationToDirPath.delete(failedLookupDirectory); } } else { @@ -455,19 +473,22 @@ namespace ts { function onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath: Path, fileOrFolder: Path) { const watches = directoryWatchesOfFailedLookups.get(dirPath); - const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); - if (isFailedLookupFile) { + if (watches.mapLocations.has(fileOrFolder)) { + const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder; invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder); + return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles; } - return isFailedLookupFile; + return false; } function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) { + const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; const isInDirPath: (location: string) => boolean = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); + filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles; } function closeTypeRootsWatch() { From 345f36d2eac921a39bda7fb0060d27e133110ad3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 31 Aug 2017 11:35:42 -0700 Subject: [PATCH 084/144] Update tests --- .../unittests/tsserverProjectSystem.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 22623ad1c8b..ae227bb683c 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -247,6 +247,23 @@ namespace ts.projectSystem { checkFileNames(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); } + function getNodeModuleDirectories(dir: string) { + const result: string[] = []; + while (true) { + result.push(combinePaths(dir, "node_modules")); + const parentDir = getDirectoryPath(dir); + if (parentDir === dir) { + break; + } + dir = parentDir; + } + return result; + } + + function getNumberOfWatchesInvokedForRecursiveWatches(recursiveWatchedDirs: string[], file: string) { + return countWhere(recursiveWatchedDirs, dir => file.length > dir.length && startsWith(file, dir) && file[dir.length] === directorySeparator); + } + /** * Test server cancellation token used to mock host token cancellation requests. * The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls @@ -375,7 +392,7 @@ namespace ts.projectSystem { const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, ["/", typeRootFromTsserverLocation], /*recursive*/ true); + checkWatchedDirectories(host, ["/a/b/c", typeRootFromTsserverLocation], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -4243,7 +4260,7 @@ namespace ts.projectSystem { const { configFileName } = projectService.openClientFile(file1.path); assert.equal(configFileName, tsconfigFile.path, `should find config`); checkNumberOfConfiguredProjects(projectService, 1); - const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, canonicalFrontendDir, "/"]; + const watchingRecursiveDirectories = [`${canonicalFrontendDir}/src`, canonicalFrontendDir].concat(getNodeModuleDirectories(getDirectoryPath(canonicalFrontendDir))); const project = projectService.configuredProjects.get(canonicalConfigPath); verifyProjectAndWatchedDirectories(); @@ -4256,7 +4273,8 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); - callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", [canonicalFile3Path], watchingRecursiveDirectories.length); + const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); + callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", [canonicalFile3Path], numberOfTimesWatchInvoked); // Called for type root resolution const directoryExistsCalled = createMap(); @@ -4266,7 +4284,7 @@ namespace ts.projectSystem { directoryExistsCalled.set(`/node_modules`, 2); directoryExistsCalled.set(`${frontendDir}/types`, 2); directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); - directoryExistsCalled.set(canonicalFile3Path, watchingRecursiveDirectories.length); + directoryExistsCalled.set(canonicalFile3Path, numberOfTimesWatchInvoked); callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); callsTrackingHost.verifyNoCall("getDirectories"); @@ -4353,7 +4371,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName } = projectService.openClientFile(app.path); assert.equal(configFileName, tsconfigJson.path, `should find config`); - const recursiveWatchedDirectories: string[] = [appFolder, "/"]; + const recursiveWatchedDirectories: string[] = [appFolder].concat(getNodeModuleDirectories(getDirectoryPath(appFolder))); verifyProject(); let timeoutAfterReloadFs = timeoutDuringPartialInstallation; From 2b97b2c04defa521fd15a587950eeed1f1736399 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 31 Aug 2017 11:35:55 -0700 Subject: [PATCH 085/144] Print number of files in the project when printing project --- src/server/project.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index 7dc236e8edf..3fb2d74795f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -889,10 +889,11 @@ namespace ts.server { filesToString() { if (!this.program) { - return ""; + return "\tFiles (0)\n"; } - let strBuilder = ""; - for (const file of this.program.getSourceFiles()) { + const sourceFiles = this.program.getSourceFiles(); + let strBuilder = `\tFiles (${sourceFiles.length})\n`; + for (const file of sourceFiles) { strBuilder += `\t${file.fileName}\n`; } return strBuilder; From 8d5d4c2a0e01264f842b0608ccf062247338ca97 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 31 Aug 2017 15:18:54 -0700 Subject: [PATCH 086/144] Reduce storage of maps/sets for failed lookups --- src/compiler/resolutionCache.ts | 202 ++++++++++---------------------- src/server/project.ts | 2 + 2 files changed, 67 insertions(+), 137 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index ff34a4b9a07..1782127dc9f 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -30,13 +30,6 @@ namespace ts { isInvalidated?: boolean; } - interface DirectoryWatchesOfFailedLookup { - /** watcher for the directory of failed lookup */ - watcher: FileWatcher; - /** map with key being the failed lookup location path and value being the actual location */ - mapLocations: Map; - } - export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; getCompilationSettings(): CompilerOptions; @@ -61,11 +54,10 @@ namespace ts { const perDirectoryResolvedModuleNames = createMap>(); const resolvedTypeReferenceDirectives = createMap>(); const perDirectoryResolvedTypeReferenceDirectives = createMap>(); - const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); - const directoryWatchesOfFailedLookups = createMap(); - const failedLookupLocationToDirPath = createMap(); + const directoryWatchesOfFailedLookups = createMap(); + let hasChangesInFailedLookupLocations = false; let rootDir: string; let rootPath: Path; @@ -101,9 +93,8 @@ namespace ts { } function clear() { - // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache - clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); - failedLookupLocationToDirPath.clear(); + clearMap(directoryWatchesOfFailedLookups, closeFileWatcher); + hasChangesInFailedLookupLocations = false; closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); @@ -131,6 +122,18 @@ namespace ts { } function finishCachingPerDirectoryResolution() { + if (hasChangesInFailedLookupLocations) { + const seenDirectories = createMap(); + watchFailedLookupLocationForCache(perDirectoryResolvedModuleNames, seenDirectories); + watchFailedLookupLocationForCache(perDirectoryResolvedTypeReferenceDirectives, seenDirectories); + directoryWatchesOfFailedLookups.forEach((watcher, path) => { + if (!seenDirectories.has(path)) { + watcher.close(); + } + }); + hasChangesInFailedLookupLocations = false; + } + perDirectoryResolvedModuleNames.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); } @@ -202,8 +205,9 @@ namespace ts { else { resolution = loader(name, containingFile, compilerOptions, resolutionHost); perDirectoryResolution.set(name, resolution); + hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations || + resolution.failedLookupLocations !== (existingResolution && existingResolution.failedLookupLocations); } - updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { @@ -218,11 +222,6 @@ namespace ts { resolvedModules.push(getResult(resolution)); } - // Close all the file watchers for the names that arent required any more - if (currentResolutionsInFile) { - clearMap(currentResolutionsInFile, resolution => withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher)); - } - // replace old results with a new one cache.set(path, newResolutions); return resolvedModules; @@ -262,6 +261,7 @@ namespace ts { } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { + resolutionHost.writeLog(`resolveTypeReferenceDirectives[${resolvedTypeReferenceDirectives.size}"]: " + ${containingFile}`); return resolveNamesWithLocalCache( typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, @@ -271,6 +271,7 @@ namespace ts { } function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { + resolutionHost.writeLog(`resolveModuleNames[${resolvedModuleNames.size}"]: " + ${containingFile}`); return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, @@ -279,35 +280,19 @@ namespace ts { ); } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLocationDirPath = getDirectoryPath(failedLookupLocationPath); - const cachedDirPath = failedLookupLocationToDirPath.get(failedLocationDirPath); - if (cachedDirPath) { - watchFailedLookupLocationInDirectory(cachedDirPath, failedLookupLocationPath, /*dir*/ undefined); - return; - } - - if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { - // Watch in directory of rootPath - failedLookupLocationToDirPath.set(failedLocationDirPath, rootPath); - watchFailedLookupLocationInDirectory(rootPath, failedLookupLocationPath, rootDir); - return; - } - - const { dir, dirPath } = getDirectoryToWatchWhenNotInRootPath( - getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), - getDirectoryPath(failedLookupLocationPath) - ); - failedLookupLocationToDirPath.set(failedLocationDirPath, dirPath); - watchFailedLookupLocationInDirectory(dirPath, failedLookupLocationPath, dir); - } - function isNodeModulesDirectory(dirPath: Path) { - const nodemodules = "/node_modules"; - return endsWith(dirPath, nodemodules); + return endsWith(dirPath, "/node_modules"); } - function getDirectoryToWatchWhenNotInRootPath(dir: string, dirPath: Path): { dir: string, dirPath: Path } { + type DirectoryOfFailedLookupWatch = { dir: string; dirPath: Path; }; + function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch { + if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { + return { dir: rootDir, dirPath: rootPath }; + } + + let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())); + let dirPath = getDirectoryPath(failedLookupLocationPath); + // If the directory is node_modules use it to watch if (isNodeModulesDirectory(dirPath)) { return { dir, dirPath }; @@ -337,21 +322,22 @@ namespace ts { return { dir, dirPath }; } - function watchFailedLookupLocationInDirectory(dirPath: Path, failedLookupLocationPath: Path, dir: string | undefined) { - const watches = directoryWatchesOfFailedLookups.get(dirPath); - if (watches) { - const existingCount = watches.mapLocations.get(failedLookupLocationPath) || 0; - watches.mapLocations.set(failedLookupLocationPath, existingCount + 1); - } - else { - Debug.assert(dir !== undefined); - const mapLocations = createMap(); - mapLocations.set(failedLookupLocationPath, 1); - directoryWatchesOfFailedLookups.set(dirPath, { - watcher: createDirectoryWatcher(dir, dirPath), - mapLocations - }); - } + function watchFailedLookupLocationForCache( + cache: Map>, seenDirectories: Map + ) { + cache.forEach(value => value && value.forEach( + resolution => forEach(resolution && resolution.failedLookupLocations, + failedLookupLocation => { + const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); + if (!seenDirectories.has(dirPath)) { + if (!directoryWatchesOfFailedLookups.has(dirPath)) { + directoryWatchesOfFailedLookups.set(dirPath, createDirectoryWatcher(dir, dirPath)); + } + seenDirectories.set(dirPath, true); + } + } + ) + )); } function createDirectoryWatcher(directory: string, dirPath: Path) { @@ -365,62 +351,18 @@ namespace ts { // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { - // If the location results in update to failed lookup, schedule program update - if (dirPath === fileOrFolderPath) { - if (onAddOrRemoveDirectoryOfFailedLookup(dirPath)) { - resolutionHost.onInvalidatedResolution(); - } - } - else if (onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath, fileOrFolderPath)) { + const isChangedFailedLookupLocation: (location: string) => boolean = dirPath === fileOrFolderPath ? + // If the file watched directory is created/deleted invalidate any resolution has failed lookup in this directory + location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)) : + // Otherwise only the resolutions referencing the file or folder added + location => resolutionHost.toPath(location) === fileOrFolderPath; + if (invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation)) { resolutionHost.onInvalidatedResolution(); } } }, WatchDirectoryFlags.Recursive); } - function closeFailedLookupLocationWatcher(_failedLookupLocation: string, failedLookupLocationPath: Path) { - const failedLookupDirectory = getDirectoryPath(failedLookupLocationPath); - const dirPath = failedLookupLocationToDirPath.get(failedLookupDirectory); - const watches = directoryWatchesOfFailedLookups.get(dirPath); - const refCount = watches.mapLocations.get(failedLookupLocationPath); - if (refCount === 1) { - // If this was last failed lookup location being tracked by the dir watcher, - // remove the failed lookup location path to dir Path entry - watches.mapLocations.delete(failedLookupLocationPath); - - // If there are no more files that need this watcher alive, close the watcher - if (watches.mapLocations.size === 0) { - watches.watcher.close(); - directoryWatchesOfFailedLookups.delete(dirPath); - failedLookupLocationToDirPath.delete(failedLookupDirectory); - } - } - else { - watches.mapLocations.set(failedLookupLocationPath, refCount - 1); - } - } - - type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void; - function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction, startIndex?: number) { - if (failedLookupLocations) { - for (let i = startIndex || 0; i < failedLookupLocations.length; i++) { - fn(failedLookupLocations[i], resolutionHost.toPath(failedLookupLocations[i])); - } - } - } - - function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { - const index = existingFailedLookupLocations && failedLookupLocations ? - findDiffIndex(failedLookupLocations, existingFailedLookupLocations) : - 0; - - // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation, index); - - // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher, index); - } - function invalidateResolutionCacheOfDeletedFile( deletedFilePath: Path, cache: Map>, @@ -429,9 +371,8 @@ namespace ts { cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); - value.forEach(resolution => { - withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher); - }); + hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations || + value && forEachEntry(value, resolution => !!resolution.failedLookupLocations); } else if (value) { value.forEach(resolution => { @@ -449,7 +390,12 @@ namespace ts { }); } - function invalidateResolutionCacheOfChangedFailedLookupLocation( + function invalidateResolutionOfFile(filePath: Path) { + invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); + invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); + } + + function invalidateResolutionCacheOfFailedLookupLocation( cache: Map>, isChangedFailedLookupLocation: (location: string) => boolean ) { @@ -466,29 +412,11 @@ namespace ts { }); } - function invalidateResolutionOfFile(filePath: Path) { - invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); - invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); - } - - function onFileAddOrRemoveInDirectoryOfFailedLookup(dirPath: Path, fileOrFolder: Path) { - const watches = directoryWatchesOfFailedLookups.get(dirPath); - if (watches.mapLocations.has(fileOrFolder)) { - const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; - const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder; - invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder); - invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder); - return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles; - } - return false; - } - - function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) { - const invalidatedFiles = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; - const isInDirPath: (location: string) => boolean = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)); - invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); - invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); - filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFiles; + function invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation: (location: string) => boolean) { + const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; + invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, isChangedFailedLookupLocation); + invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, isChangedFailedLookupLocation); + return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; } function closeTypeRootsWatch() { diff --git a/src/server/project.ts b/src/server/project.ts index 3fb2d74795f..24b3cd03de4 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -789,6 +789,7 @@ namespace ts.server { private updateGraphWorker() { const oldProgram = this.program; + this.writeLog(`Starting Update graph worker: Project: ${this.getProjectName()}`); this.resolutionCache.startCachingPerDirectoryResolution(); this.program = this.languageService.getProgram(); this.resolutionCache.finishCachingPerDirectoryResolution(); @@ -843,6 +844,7 @@ namespace ts.server { } }); + this.writeLog(`Finishing Update graph worker: Project: ${this.getProjectName()}`); return hasChanges; } From 9e5e20c80fdb4e8bbccfec437f4ab498db59d381 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 31 Aug 2017 17:58:09 -0700 Subject: [PATCH 087/144] Remove the configured project if on next open file if it has no open files instead of immediately when closing last open file --- src/server/editorServices.ts | 17 +++++++++++++---- src/server/project.ts | 4 ++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e6784942c66..0679ccd8393 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -878,10 +878,10 @@ namespace ts.server { if (info.hasMixedContent) { info.registerFileUpdate(); } - // last open file in configured project - close it - if ((p).deleteOpenRef() === 0) { - (projectsToRemove || (projectsToRemove = [])).push(p); - } + // Delete the reference to the open configured projects but + // do not remove the project so that we can reuse this project + // if it would need to be re-created with next file open + (p).deleteOpenRef(); } else if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) { // If this was the open root file of inferred project @@ -1881,6 +1881,15 @@ namespace ts.server { } this.addToListOfOpenFiles(info); + // Remove the configured projects that have zero references from open files. + // This was postponed from closeOpenFile to after opening next file, + // so that we can reuse the project if we need to right away + this.configuredProjects.forEach(project => { + if (!project.hasOpenRef()) { + this.removeProject(project); + } + }); + // Delete the orphan files here because there might be orphan script infos (which are not part of project) // when some file/s were closed which resulted in project removal. // It was then postponed to cleanup these script infos so that they can be reused if diff --git a/src/server/project.ts b/src/server/project.ts index 24b3cd03de4..37b0bb3cd7f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1311,6 +1311,10 @@ namespace ts.server { return this.openRefCount; } + hasOpenRef() { + return !!this.openRefCount; + } + getEffectiveTypeRoots() { return getEffectiveTypeRoots(this.getCompilationSettings(), this.partialSystem) || []; } From 13aafa26ba84b7c8af5e11886bc175db63321be4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 1 Sep 2017 11:08:00 -0700 Subject: [PATCH 088/144] Update tests --- src/harness/unittests/telemetry.ts | 22 +-- .../unittests/tsserverProjectSystem.ts | 130 +++++++++++++++--- 2 files changed, 127 insertions(+), 25 deletions(-) diff --git a/src/harness/unittests/telemetry.ts b/src/harness/unittests/telemetry.ts index e7a64c59903..7c098066321 100644 --- a/src/harness/unittests/telemetry.ts +++ b/src/harness/unittests/telemetry.ts @@ -11,18 +11,24 @@ namespace ts.projectSystem { }); it("only sends an event once", () => { - const file = makeFile("/a.ts"); - const tsconfig = makeFile("/tsconfig.json", {}); + const file = makeFile("/a/a.ts"); + const file2 = makeFile("/b.ts"); + const tsconfig = makeFile("/a/tsconfig.json", {}); - const et = new EventTracker([file, tsconfig]); + const et = new EventTracker([file, file2, tsconfig]); et.service.openClientFile(file.path); - et.assertProjectInfoTelemetryEvent({}); + et.assertProjectInfoTelemetryEvent({}, tsconfig.path); et.service.closeClientFile(file.path); - checkNumberOfProjects(et.service, { configuredProjects: 0 }); + checkNumberOfProjects(et.service, { configuredProjects: 1 }); + + et.service.openClientFile(file2.path); + checkNumberOfProjects(et.service, { inferredProjects: 1 }); + + assert.equal(et.getEvents().length, 0); et.service.openClientFile(file.path); - checkNumberOfProjects(et.service, { configuredProjects: 1 }); + checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 }); assert.equal(et.getEvents().length, 0); }); @@ -249,9 +255,9 @@ namespace ts.projectSystem { return events; } - assertProjectInfoTelemetryEvent(partial: Partial): void { + assertProjectInfoTelemetryEvent(partial: Partial, configFile?: string): void { assert.deepEqual(this.getEvent(ts.server.ProjectInfoTelemetryEvent), { - projectId: Harness.mockHash("/tsconfig.json"), + projectId: Harness.mockHash(configFile || "/tsconfig.json"), fileStats: fileStats({ ts: 1 }), compilerOptions: {}, extends: false, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index ae227bb683c..d7de270645b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -958,7 +958,42 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); }); - it("should close configured project after closing last open file", () => { + it("should resuse same project if file is opened from the configured project that has no open files", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/main2.ts", + content: "let y =1;" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts", "main2.ts" ] + }` + }; + const host = createServerHost([file1, file2, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path); + + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.equal(project.openRefCount, 0); + + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.equal(project.openRefCount, 1); + }); + + it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { const file1 = { path: "/a/b/main.ts", content: "let x =1;" @@ -976,8 +1011,14 @@ namespace ts.projectSystem { const projectService = createProjectService(host, { useSingleInferredProject: true }); projectService.openClientFile(file1.path); checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path); projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.equal(project.openRefCount, 0); + + projectService.openClientFile(libFile.path); checkNumberOfConfiguredProjects(projectService, 0); }); @@ -1058,23 +1099,43 @@ namespace ts.projectSystem { }); checkNumberOfProjects(projectService, { configuredProjects: 2 }); + const proj1 = projectService.configuredProjects.get(config1.path); + const proj2 = projectService.configuredProjects.get(config2.path); + assert.isDefined(proj1); + assert.isDefined(proj2); // open client file - should not lead to creation of inferred project projectService.openClientFile(file1.path, file1.content); checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); projectService.openClientFile(file3.path, file3.content); checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); projectService.closeExternalProject(externalProjectName); // open file 'file1' from configured project keeps project alive checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); projectService.closeClientFile(file3.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, {}); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); + + projectService.openClientFile(file2.path, file2.content); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config1.path)); + assert.isDefined(projectService.configuredProjects.get(config2.path)); + }); it("reload regular file after closing", () => { @@ -1177,16 +1238,21 @@ namespace ts.projectSystem { path: "/a/b/f1.ts", content: "let x = 1" }; + const file2 = { + path: "/a/f2.ts", + content: "let x = 1" + }; const configFile = { path: "/a/b/tsconfig.json", content: JSON.stringify({ compilerOptions: {} }) }; const externalProjectName = "externalproject"; - const host = createServerHost([file1, configFile]); + const host = createServerHost([file1, file2, libFile, configFile]); const projectService = createProjectService(host); projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); projectService.openExternalProject({ rootFiles: toExternalFiles([configFile.path]), @@ -1195,13 +1261,20 @@ namespace ts.projectSystem { }); checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); projectService.closeExternalProject(externalProjectName); // configured project is alive since file is still open checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, {}); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(configFile.path)); }); it("changes in closed files are reflected in project structure", () => { @@ -1607,11 +1680,12 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); + // Should close configured project with next file open projectService.closeClientFile(f1.path); projectService.openClientFile(f2.path); - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config.path)); checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); }); @@ -1910,15 +1984,16 @@ namespace ts.projectSystem { projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project1 = configuredProjectAt(projectService, 0); + const project1 = projectService.configuredProjects.get(tsconfig1.path); assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 1"); assert.equal(project1.getScriptInfo(file2.path).containingProjects.length, 1, "containing projects count"); projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 2 }); assert.equal(project1.openRefCount, 2, "Open ref count in project1 - 2"); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); - const project2 = configuredProjectAt(projectService, 1); + const project2 = projectService.configuredProjects.get(tsconfig2.path); assert.equal(project2.openRefCount, 1, "Open ref count in project2 - 2"); assert.equal(project1.getScriptInfo(file1.path).containingProjects.length, 2, `${file1.path} containing projects count`); @@ -1928,9 +2003,21 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 2 }); assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 3"); assert.equal(project2.openRefCount, 1, "Open ref count in project2 - 3"); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2); projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 0 }); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.equal(project1.openRefCount, 0, "Open ref count in project1 - 4"); + assert.equal(project2.openRefCount, 0, "Open ref count in project2 - 4"); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.strictEqual(projectService.configuredProjects.get(tsconfig2.path), project2); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(tsconfig1.path), project1); + assert.isUndefined(projectService.configuredProjects.get(tsconfig2.path)); + assert.equal(project1.openRefCount, 1, "Open ref count in project1 - 5"); }); it("language service disabled state is updated in external projects", () => { @@ -2000,14 +2087,18 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path); projectService.closeClientFile(f1.path); - projectService.checkNumberOfProjects({}); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.equal(project.openRefCount, 0); for (const f of [f1, f2, f3]) { // There shouldnt be any script info as we closed the file that resulted in creation of it const scriptInfo = projectService.getScriptInfoForNormalizedPath(server.toNormalizedPath(f.path)); - assert.equal(scriptInfo.containingProjects.length, 0, `expect 0 containing projects for '${f.path}'`); + assert.equal(scriptInfo.containingProjects.length, 1, `expect 1 containing projects for '${f.path}'`); + assert.equal(scriptInfo.containingProjects[0], project, `expect configured project to be the only containing project for '${f.path}'`); } }); @@ -2551,12 +2642,18 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); + const project = projectService.configuredProjects.get(config.path); + assert.equal(project.openRefCount, 1); projectService.closeClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 0 }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.equal(project.openRefCount, 0); projectService.openClientFile(f.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config.path), project); + assert.equal(project.openRefCount, 1); }); }); @@ -3237,12 +3334,10 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(file1.path); host.runQueuedTimeoutCallbacks(); - checkNumberOfConfiguredProjects(projectService, 1); + // Since there is no file open from configFile it would be closed + checkNumberOfConfiguredProjects(projectService, 0); checkNumberOfInferredProjects(projectService, 1); - const configuredProject = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configuredProject, [configFile.path]); - const inferredProject = projectService.inferredProjects[0]; assert.isTrue(inferredProject.containsFile(file1.path)); }); @@ -3271,7 +3366,8 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(f.path); - projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + // Since no file from the configured project is open, it would be closed immediately + projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); }); }); From 6c6129361f964cae2d0a80c9f7da25d8375af2f8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 1 Sep 2017 16:22:37 -0700 Subject: [PATCH 089/144] Test to verify calls to isProgramUptoDate return true when there is no change in compiler options --- .../unittests/reuseProgramStructure.ts | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index f2e269763ab..dee4d749487 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -869,4 +869,172 @@ namespace ts { createProgram([], {}); }); }); + + import TestSystem = ts.TestFSWithWatch.TestServerHost; + type FileOrFolder = ts.TestFSWithWatch.FileOrFolder; + import createTestSystem = ts.TestFSWithWatch.createWatchedSystem; + import libFile = ts.TestFSWithWatch.libFile; + + describe("isProgramUptoDate should return true when there is no change in compiler options and", () => { + function verifyProgramIsUptoDate( + program: Program, + newRootFileNames: string[], + newOptions: CompilerOptions + ) { + const actual = isProgramUptoDate( + program, newRootFileNames, newOptions, + path => program.getSourceFileByPath(path).version, /*fileExists*/ returnFalse, + /*hasInvalidatedResolution*/ returnFalse, + /*hasChangedAutomaticTypeDirectiveNames*/ returnFalse + ); + assert.isTrue(actual); + } + + function duplicate(options: CompilerOptions): CompilerOptions; + function duplicate(fileNames: string[]): string[]; + function duplicate(filesOrOptions: CompilerOptions | string[]) { + return JSON.parse(JSON.stringify(filesOrOptions)); + } + + function createWatchingSystemHost(host: TestSystem) { + return ts.createWatchingSystemHost(/*pretty*/ undefined, host); + } + + function verifyProgramWithoutConfigFile(watchingSystemHost: WatchingSystemHost, rootFiles: string[], options: CompilerOptions) { + const program = createWatchModeWithoutConfigFile(rootFiles, options, watchingSystemHost)(); + verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); + } + + function getConfigParseResult(watchingSystemHost: WatchingSystemHost, configFileName: string) { + return parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); + } + + function verifyProgramWithConfigFile(watchingSystemHost: WatchingSystemHost, configFile: string) { + const result = getConfigParseResult(watchingSystemHost, configFile); + const program = createWatchModeWithConfigFile(result, {}, watchingSystemHost)(); + const { fileNames, options } = getConfigParseResult(watchingSystemHost, configFile); + verifyProgramIsUptoDate(program, fileNames, options); + } + + function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) { + const watchingSystemHost = createWatchingSystemHost(createTestSystem(files)); + verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options); + verifyProgramWithConfigFile(watchingSystemHost, configFile); + } + + it("has empty options", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: "let x = 1" + }; + const file2: FileOrFolder = { + path: "/a/b/file2.ts", + content: "let y = 1" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + verifyProgram([file1, file2, libFile, configFile], [file1.path, file2.path], {}, configFile.path); + }); + + it("has lib specified in the options", () => { + const compilerOptions: CompilerOptions = { lib: ["es5", "es2015.promise"] }; + const app: FileOrFolder = { + path: "/src/app.ts", + content: "var x: Promise;" + }; + const configFile: FileOrFolder = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + const es5Lib: FileOrFolder = { + path: "/compiler/lib.es5.d.ts", + content: "declare const eval: any" + }; + const es2015Promise: FileOrFolder = { + path: "/compiler/lib.es2015.promise.d.ts", + content: "declare class Promise {}" + }; + + verifyProgram([app, configFile, es5Lib, es2015Promise], [app.path], compilerOptions, configFile.path); + }); + + it("has paths specified in the options", () => { + const compilerOptions: CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: FileOrFolder = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ + import classD from "module3/file3";\ + let x = new classc();\ + let y = new classD();' + }; + const module1: FileOrFolder = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: FileOrFolder = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: FileOrFolder = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: FileOrFolder = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions }) + }; + + verifyProgram([app, module1, module2, module3, libFile, configFile], [app.path], compilerOptions, configFile.path); + }); + + it("has include paths specified in tsconfig file", () => { + const compilerOptions: CompilerOptions = { + baseUrl: ".", + paths: { + "*": [ + "packages/mail/data/*", + "packages/styles/*", + "*" + ] + } + }; + const app: FileOrFolder = { + path: "/src/packages/framework/app.ts", + content: 'import classc from "module1/lib/file1";\ + import classD from "module3/file3";\ + let x = new classc();\ + let y = new classD();' + }; + const module1: FileOrFolder = { + path: "/src/packages/mail/data/module1/lib/file1.ts", + content: 'import classc from "module2/file2";export default classc;', + }; + const module2: FileOrFolder = { + path: "/src/packages/mail/data/module1/lib/module2/file2.ts", + content: 'class classc { method2() { return "hello"; } }\nexport default classc', + }; + const module3: FileOrFolder = { + path: "/src/packages/styles/module3/file3.ts", + content: "class classD { method() { return 10; } }\nexport default classD;" + }; + const configFile: FileOrFolder = { + path: "/src/tsconfig.json", + content: JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] }) + }; + + const watchingSystemHost = createWatchingSystemHost(createTestSystem([app, module1, module2, module3, libFile, configFile])); + verifyProgramWithConfigFile(watchingSystemHost, configFile.path); + }); + }); } From 7b2bab5b86cf24ad11417b19de42551f941de548 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Sep 2017 14:19:40 -0700 Subject: [PATCH 090/144] Revert to use refcount to keep track of directory watchers for failed lookup --- src/compiler/resolutionCache.ts | 172 ++++++++++++++++++-------------- src/server/editorServices.ts | 5 +- src/server/project.ts | 10 +- 3 files changed, 104 insertions(+), 83 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 1782127dc9f..3d5d3e0a845 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -43,6 +43,13 @@ namespace ts { writeLog(s: string): void; } + interface DirectoryWatchesOfFailedLookup { + /** watcher for the directory of failed lookup */ + watcher: FileWatcher; + /** ref count keeping this directory watch alive */ + refCount: number; + } + export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; @@ -56,8 +63,7 @@ namespace ts { const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); - const directoryWatchesOfFailedLookups = createMap(); - let hasChangesInFailedLookupLocations = false; + const directoryWatchesOfFailedLookups = createMap(); let rootDir: string; let rootPath: Path; @@ -93,8 +99,7 @@ namespace ts { } function clear() { - clearMap(directoryWatchesOfFailedLookups, closeFileWatcher); - hasChangesInFailedLookupLocations = false; + clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); @@ -122,17 +127,12 @@ namespace ts { } function finishCachingPerDirectoryResolution() { - if (hasChangesInFailedLookupLocations) { - const seenDirectories = createMap(); - watchFailedLookupLocationForCache(perDirectoryResolvedModuleNames, seenDirectories); - watchFailedLookupLocationForCache(perDirectoryResolvedTypeReferenceDirectives, seenDirectories); - directoryWatchesOfFailedLookups.forEach((watcher, path) => { - if (!seenDirectories.has(path)) { - watcher.close(); - } - }); - hasChangesInFailedLookupLocations = false; - } + directoryWatchesOfFailedLookups.forEach((watcher, path) => { + if (watcher.refCount === 0) { + directoryWatchesOfFailedLookups.delete(path); + watcher.watcher.close(); + } + }); perDirectoryResolvedModuleNames.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); @@ -171,7 +171,7 @@ namespace ts { logChanges: boolean): R[] { const path = resolutionHost.toPath(containingFile); - const currentResolutionsInFile = cache.get(path); + const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path); const dirPath = getDirectoryPath(path); let perDirectoryResolution = perDirectoryCache.get(dirPath); if (!perDirectoryResolution) { @@ -179,53 +179,67 @@ namespace ts { perDirectoryCache.set(dirPath, perDirectoryResolution); } - const newResolutions: Map = createMap(); const resolvedModules: R[] = []; const compilerOptions = resolutionHost.getCompilationSettings(); + const seenNamesInFile = createMap(); for (const name of names) { // check if this is a duplicate entry in the list - let resolution = newResolutions.get(name); - if (!resolution) { - const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name); - if (existingResolution) { - // Remove from the cache since we would update the resolution in new file ourselves - currentResolutionsInFile.delete(name); - } - - if (moduleResolutionIsValid(existingResolution)) { - // ok, it is safe to use existing name resolution results - resolution = existingResolution; + let resolution = resolutionsInFile.get(name); + if (!moduleResolutionIsValid(resolution, name)) { + const existingResolution = resolution; + const resolutionInDirectory = perDirectoryResolution.get(name); + if (resolutionInDirectory) { + resolution = resolutionInDirectory; } else { - const resolutionInDirectory = perDirectoryResolution && perDirectoryResolution.get(name); - if (resolutionInDirectory) { - resolution = resolutionInDirectory; - } - else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost); - perDirectoryResolution.set(name, resolution); - hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations || - resolution.failedLookupLocations !== (existingResolution && existingResolution.failedLookupLocations); - } + resolution = loader(name, containingFile, compilerOptions, resolutionHost); + perDirectoryResolution.set(name, resolution); } - newResolutions.set(name, resolution); + resolutionsInFile.set(name, resolution); + const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations); + watchFailedLookupLocationOfResolution(resolution, diffIndex || 0); + stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0); if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { filesWithChangedSetOfUnresolvedImports.push(path); // reset log changes to avoid recording the same file multiple times logChanges = false; } } - - Debug.assert(resolution !== undefined); - + Debug.assert(resolution !== undefined && !resolution.isInvalidated); + seenNamesInFile.set(name, true); resolvedModules.push(getResult(resolution)); } - // replace old results with a new one - cache.set(path, newResolutions); + // Stop watching and remove the unused name + resolutionsInFile.forEach((resolution, name) => { + if (!seenNamesInFile.has(name)) { + stopWatchFailedLookupLocationOfResolution(resolution); + resolutionsInFile.delete(name); + } + }); + return resolvedModules; + function moduleResolutionIsValid(resolution: T, name: string): boolean { + // This is already calculated resolution in this round of synchronization + if (seenNamesInFile.has(name)) { + return true; + } + + if (!resolution || resolution.isInvalidated) { + return false; + } + + const result = getResult(resolution); + if (result) { + return true; + } + // consider situation if we have no candidate locations as valid resolution. + // after all there is no point to invalidate it if we have no idea where to look for the module. + return resolution.failedLookupLocations.length === 0; + } + function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean { if (oldResolution === newResolution) { return true; @@ -243,25 +257,9 @@ namespace ts { } return getResultFileName(oldResult) === getResultFileName(newResult); } - - function moduleResolutionIsValid(resolution: T): boolean { - if (!resolution || resolution.isInvalidated) { - return false; - } - - const result = getResult(resolution); - if (result) { - return true; - } - - // consider situation if we have no candidate locations as valid resolution. - // after all there is no point to invalidate it if we have no idea where to look for the module. - return resolution.failedLookupLocations.length === 0; - } } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - resolutionHost.writeLog(`resolveTypeReferenceDirectives[${resolvedTypeReferenceDirectives.size}"]: " + ${containingFile}`); return resolveNamesWithLocalCache( typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, @@ -271,7 +269,6 @@ namespace ts { } function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { - resolutionHost.writeLog(`resolveModuleNames[${resolvedModuleNames.size}"]: " + ${containingFile}`); return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, @@ -322,22 +319,42 @@ namespace ts { return { dir, dirPath }; } - function watchFailedLookupLocationForCache( - cache: Map>, seenDirectories: Map + function watchFailedLookupLocationOfResolution( + resolution: T, startIndex?: number ) { - cache.forEach(value => value && value.forEach( - resolution => forEach(resolution && resolution.failedLookupLocations, - failedLookupLocation => { - const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); - if (!seenDirectories.has(dirPath)) { - if (!directoryWatchesOfFailedLookups.has(dirPath)) { - directoryWatchesOfFailedLookups.set(dirPath, createDirectoryWatcher(dir, dirPath)); - } - seenDirectories.set(dirPath, true); - } + if (resolution && resolution.failedLookupLocations) { + for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) { + const failedLookupLocation = resolution.failedLookupLocations[i]; + const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + dirWatcher.refCount++; } - ) - )); + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); + } + } + } + } + + function stopWatchFailedLookupLocationOfResolution( + resolution: T + ) { + stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + } + + function stopWatchFailedLookupLocationOfResolutionFrom( + resolution: T, startIndex: number + ) { + if (resolution && resolution.failedLookupLocations) { + for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) { + const failedLookupLocation = resolution.failedLookupLocations[i]; + const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; + } + } } function createDirectoryWatcher(directory: string, dirPath: Path) { @@ -371,8 +388,9 @@ namespace ts { cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); - hasChangesInFailedLookupLocations = hasChangesInFailedLookupLocations || - value && forEachEntry(value, resolution => !!resolution.failedLookupLocations); + if (value) { + value.forEach(stopWatchFailedLookupLocationOfResolution); + } } else if (value) { value.forEach(resolution => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0679ccd8393..101f9fbb720 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1214,16 +1214,17 @@ namespace ts.server { } private printProjects() { - if (!this.logger.hasLevel(LogLevel.verbose)) { + if (!this.logger.hasLevel(LogLevel.normal)) { return; } + const writeProjectFileNames = this.logger.hasLevel(LogLevel.verbose); this.logger.startGroup(); let counter = 0; const printProjects = (projects: Project[], counter: number): number => { for (const project of projects) { this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`); - this.logger.info(project.filesToString()); + this.logger.info(project.filesToString(writeProjectFileNames)); this.logger.info("-----------------------------------------------"); counter++; } diff --git a/src/server/project.ts b/src/server/project.ts index 37b0bb3cd7f..afeb533550b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -576,7 +576,7 @@ namespace ts.server { if (!this.languageServiceEnabled) { return undefined; } - return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed); + return this.getLanguageService(/*ensureSynchronized*/ false).getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { @@ -889,14 +889,16 @@ namespace ts.server { return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName)); } - filesToString() { + filesToString(writeProjectFileNames: boolean) { if (!this.program) { return "\tFiles (0)\n"; } const sourceFiles = this.program.getSourceFiles(); let strBuilder = `\tFiles (${sourceFiles.length})\n`; - for (const file of sourceFiles) { - strBuilder += `\t${file.fileName}\n`; + if (writeProjectFileNames) { + for (const file of sourceFiles) { + strBuilder += `\t${file.fileName}\n`; + } } return strBuilder; } From 54f64a1695180a5d17c87169cc2fc385ee078d1f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Sep 2017 17:36:19 -0700 Subject: [PATCH 091/144] Resolution is valid unless it is invalidated --- Jakefile.js | 1 - src/compiler/moduleNameResolver.ts | 17 -- src/compiler/resolutionCache.ts | 23 +- src/compiler/utilities.ts | 17 ++ src/harness/tsconfig.json | 1 - .../unittests/cachingInServerLSHost.ts | 203 ------------------ .../unittests/tsserverProjectSystem.ts | 197 ++++++++++++++--- 7 files changed, 188 insertions(+), 271 deletions(-) delete mode 100644 src/harness/unittests/cachingInServerLSHost.ts diff --git a/Jakefile.js b/Jakefile.js index 46a744445d3..9e334a9bded 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -121,7 +121,6 @@ var harnessSources = harnessCoreSources.concat([ "transpile.ts", "reuseProgramStructure.ts", "textStorage.ts", - "cachingInServerLSHost.ts", "moduleResolution.ts", "tsconfigParsing.ts", "commandLineParsing.ts", diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 4d4fc6c1d8d..52c16f7a646 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1127,21 +1127,4 @@ namespace ts { function toSearchResult(value: T | undefined): SearchResult { return value !== undefined ? { value } : undefined; } - - /** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */ - function forEachAncestorDirectory(directory: string, callback: (directory: string) => SearchResult): SearchResult { - while (true) { - const result = callback(directory); - if (result !== undefined) { - return result; - } - - const parentPath = getDirectoryPath(directory); - if (parentPath === directory) { - return undefined; - } - - directory = parentPath; - } - } } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 3d5d3e0a845..56dd48aaebf 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -184,9 +184,9 @@ namespace ts { const seenNamesInFile = createMap(); for (const name of names) { - // check if this is a duplicate entry in the list let resolution = resolutionsInFile.get(name); - if (!moduleResolutionIsValid(resolution, name)) { + // Resolution is valid if it is present and not invalidated + if (!resolution || resolution.isInvalidated) { const existingResolution = resolution; const resolutionInDirectory = perDirectoryResolution.get(name); if (resolutionInDirectory) { @@ -221,25 +221,6 @@ namespace ts { return resolvedModules; - function moduleResolutionIsValid(resolution: T, name: string): boolean { - // This is already calculated resolution in this round of synchronization - if (seenNamesInFile.has(name)) { - return true; - } - - if (!resolution || resolution.isInvalidated) { - return false; - } - - const result = getResult(resolution); - if (result) { - return true; - } - // consider situation if we have no candidate locations as valid resolution. - // after all there is no point to invalidate it if we have no idea where to look for the module. - return resolution.failedLookupLocations.length === 0; - } - function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean { if (oldResolution === newResolution) { return true; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 19748bfe0de..e17e0e96997 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3609,6 +3609,23 @@ namespace ts { export function closeFileWatcherOf(objWithWatcher: T) { objWithWatcher.watcher.close(); } + + /** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */ + export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T): T { + while (true) { + const result = callback(directory); + if (result !== undefined) { + return result; + } + + const parentPath = getDirectoryPath(directory); + if (parentPath === directory) { + return undefined; + } + + directory = parentPath; + } + } } namespace ts { diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 7c3ad0bc5a3..006359c8022 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -109,7 +109,6 @@ "./unittests/convertToBase64.ts", "./unittests/transpile.ts", "./unittests/reuseProgramStructure.ts", - "./unittests/cachingInServerLSHost.ts", "./unittests/moduleResolution.ts", "./unittests/tsconfigParsing.ts", "./unittests/commandLineParsing.ts", diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts deleted file mode 100644 index a1095e7dc48..00000000000 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ /dev/null @@ -1,203 +0,0 @@ -/// - -namespace ts { - interface File { - name: string; - content: string; - } - - function createDefaultServerHost(fileMap: Map): server.ServerHost { - const existingDirectories = createMap(); - forEachKey(fileMap, name => { - let dir = getDirectoryPath(name); - let previous: string; - do { - existingDirectories.set(dir, true); - previous = dir; - dir = getDirectoryPath(dir); - } while (dir !== previous); - }); - return { - args: [], - newLine: "\r\n", - useCaseSensitiveFileNames: false, - write: noop, - readFile: path => { - const file = fileMap.get(path); - return file && file.content; - }, - writeFile: notImplemented, - resolvePath: notImplemented, - fileExists: path => fileMap.has(path), - directoryExists: path => existingDirectories.get(path) || false, - createDirectory: noop, - getExecutingFilePath: () => "", - getCurrentDirectory: () => "", - getDirectories: () => [], - getEnvironmentVariable: () => "", - readDirectory: notImplemented, - exit: noop, - watchFile: () => ({ - close: noop - }), - watchDirectory: () => ({ - close: noop - }), - setTimeout, - clearTimeout, - setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0), - clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout, - createHash: Harness.mockHash, - }; - } - - function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } { - const svcOpts: server.ProjectServiceOptions = { - host: serverHost, - logger: projectSystem.nullLogger, - cancellationToken: { isCancellationRequested: () => false }, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined - }; - const projectService = new server.ProjectService(svcOpts); - const rootScriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(server.toNormalizedPath(rootFile), /*openedByClient*/ true); - - const project = projectService.assignOrphanScriptInfoToInferredProject(rootScriptInfo); - project.setCompilerOptions({ module: ts.ModuleKind.AMD, noLib: true } ); - return { - project, - rootScriptInfo - }; - } - - describe("Caching in LSHost", () => { - it("works using legacy resolution logic", () => { - const root: File = { - name: "c:/d/f0.ts", - content: `import {x} from "f1"` - }; - - const imported: File = { - name: "c:/f1.ts", - content: `foo()` - }; - - const serverHost = createDefaultServerHost(createMapFromTemplate({ [root.name]: root, [imported.name]: imported })); - const { project, rootScriptInfo } = createProject(root.name, serverHost); - - // ensure that imported file was found - let diags = project.getLanguageService().getSemanticDiagnostics(imported.name); - assert.equal(diags.length, 1); - - - const originalFileExists = serverHost.fileExists; - { - // patch fileExists to make sure that disk is not touched - serverHost.fileExists = notImplemented; - - const newContent = `import {x} from "f1" - var x: string = 1;`; - rootScriptInfo.editContent(0, root.content.length, newContent); - // trigger synchronization to make sure that import will be fetched from the cache - diags = project.getLanguageService().getSemanticDiagnostics(imported.name); - // ensure file has correct number of errors after edit - assert.equal(diags.length, 1); - } - { - let fileExistsIsCalled = false; - serverHost.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsIsCalled = true; - assert.isTrue(fileName.indexOf("/f2.") !== -1); - return originalFileExists.call(serverHost, fileName); - }; - const newContent = `import {x} from "f2"`; - rootScriptInfo.editContent(0, root.content.length, newContent); - - try { - // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk - project.getLanguageService().getSemanticDiagnostics(imported.name); - assert.isTrue(false, `should not find file '${imported.name}'`); - } - catch (e) { - assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0); - } - - assert.isTrue(fileExistsIsCalled); - } - { - let fileExistsCalled = false; - serverHost.fileExists = (fileName): boolean => { - if (fileName === "lib.d.ts") { - return false; - } - fileExistsCalled = true; - assert.isTrue(fileName.indexOf("/f1.") !== -1); - return originalFileExists.call(serverHost, fileName); - }; - - const newContent = `import {x} from "f1"`; - rootScriptInfo.editContent(0, root.content.length, newContent); - project.getLanguageService().getSemanticDiagnostics(imported.name); - assert.isTrue(fileExistsCalled); - - // setting compiler options discards module resolution cache - fileExistsCalled = false; - - const compilerOptions = ts.cloneCompilerOptions(project.getCompilationSettings()); - compilerOptions.target = ts.ScriptTarget.ES5; - project.setCompilerOptions(compilerOptions); - - project.getLanguageService().getSemanticDiagnostics(imported.name); - assert.isTrue(fileExistsCalled); - } - }); - - it("loads missing files from disk", () => { - const root: File = { - name: `c:/foo.ts`, - content: `import {x} from "bar"` - }; - - const imported: File = { - name: `c:/bar.d.ts`, - content: `export var y = 1` - }; - - const fileMap = createMapFromTemplate({ [root.name]: root }); - const serverHost = createDefaultServerHost(fileMap); - const originalFileExists = serverHost.fileExists; - - let fileExistsCalledForBar = false; - serverHost.fileExists = fileName => { - if (fileName === "lib.d.ts") { - return false; - } - if (!fileExistsCalledForBar) { - fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1; - } - - return originalFileExists.call(serverHost, fileName); - }; - - const { project, rootScriptInfo } = createProject(root.name, serverHost); - - let diags = project.getLanguageService().getSemanticDiagnostics(root.name); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); - assert.isTrue(diags.length === 1, "one diagnostic expected"); - const messageText = diags[0].messageText; - assert.isTrue(isString(messageText) && messageText.indexOf("Cannot find module") === 0, "should be 'cannot find module' message"); - - fileMap.set(imported.name, imported); - fileExistsCalledForBar = false; - rootScriptInfo.editContent(0, root.content.length, `import {y} from "bar"`); - - diags = project.getLanguageService().getSemanticDiagnostics(root.name); - assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called."); - assert.isTrue(diags.length === 0, "The import should succeed once the imported file appears on disk."); - }); - }); -} diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index d7de270645b..7c04f08938d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -249,14 +249,9 @@ namespace ts.projectSystem { function getNodeModuleDirectories(dir: string) { const result: string[] = []; - while (true) { - result.push(combinePaths(dir, "node_modules")); - const parentDir = getDirectoryPath(dir); - if (parentDir === dir) { - break; - } - dir = parentDir; - } + forEachAncestorDirectory(dir, ancestor => { + result.push(combinePaths(ancestor, "node_modules")); + }); return result; } @@ -343,14 +338,9 @@ namespace ts.projectSystem { export function getTypeRootsFromLocation(currentDirectory: string) { currentDirectory = normalizePath(currentDirectory); const result: string[] = []; - while (true) { - result.push(combinePaths(currentDirectory, "node_modules/@types")); - const parentDirectory = getDirectoryPath(currentDirectory); - if (parentDirectory === currentDirectory) { - break; - } - currentDirectory = parentDirectory; - } + forEachAncestorDirectory(currentDirectory, ancestor => { + result.push(combinePaths(ancestor, "node_modules/@types")); + }); return result; } @@ -4147,6 +4137,7 @@ namespace ts.projectSystem { verifyCalledOnEachEntry, verifyNoHostCalls, verifyNoHostCallsExceptFileExistsOnce, + verifyCalledOn, clear }; @@ -4179,6 +4170,12 @@ namespace ts.projectSystem { } } + function verifyCalledOn(callback: keyof CalledMaps, name: string) { + const calledMap = calledMaps[callback]; + const result = calledMap.get(name); + assert.isTrue(result && !!result.length, `${callback} should be called with name: ${name}: ${arrayFrom(calledMap.keys())}`); + } + function verifyNoCall(callback: keyof CalledMaps) { const calledMap = calledMaps[callback]; assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`); @@ -4218,6 +4215,161 @@ namespace ts.projectSystem { } } + it("works using legacy resolution logic", () => { + let rootContent = `import {x} from "f1"`; + const root: FileOrFolder = { + path: "/c/d/f0.ts", + content: rootContent + }; + + const imported: FileOrFolder = { + path: "/c/f1.ts", + content: `foo()` + }; + + const host = createServerHost([root, imported]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + // ensure that imported file was found + verifyImportedDiagnostics(); + + const callsTrackingHost = createCallsTrackingHost(host); + + // trigger synchronization to make sure that import will be fetched from the cache + // ensure file has correct number of errors after edit + editContent(`import {x} from "f1"; + var x: string = 1;`); + verifyImportedDiagnostics(); + callsTrackingHost.verifyNoHostCalls(); + + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + editContent(`import {x} from "f2"`); + try { + // trigger synchronization to make sure that LSHost will try to find 'f2' module on disk + verifyImportedDiagnostics(); + assert.isTrue(false, `should not find file '${imported.path}'`); + } + catch (e) { + assert.isTrue(e.message.indexOf(`Could not find file: '${imported.path}'.`) === 0); + } + const f2Lookups = getLocationsForModuleLookup("f2"); + callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", f2Lookups, 1); + const f2DirLookups = getLocationsForDirectoryLookup(); + callsTrackingHost.verifyCalledOnEachEntry("directoryExists", f2DirLookups); + callsTrackingHost.verifyNoCall("getDirectories"); + callsTrackingHost.verifyNoCall("readFile"); + callsTrackingHost.verifyNoCall("readDirectory"); + + editContent(`import {x} from "f1"`); + verifyImportedDiagnostics(); + const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); + f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; + const f1DirLookups = ["/c/d", "/c", typeRootFromTsserverLocation]; + vertifyF1Lookups(); + + // setting compiler options discards module resolution cache + callsTrackingHost.clear(); + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true, target: ts.ScriptTarget.ES5 }); + verifyImportedDiagnostics(); + vertifyF1Lookups(); + + function vertifyF1Lookups() { + callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", f1Lookups, 1); + callsTrackingHost.verifyCalledOnEachEntryNTimes("directoryExists", f1DirLookups, 1); + callsTrackingHost.verifyNoCall("getDirectories"); + callsTrackingHost.verifyNoCall("readFile"); + callsTrackingHost.verifyNoCall("readDirectory"); + } + + function editContent(newContent: string) { + callsTrackingHost.clear(); + rootScriptInfo.editContent(0, rootContent.length, newContent); + rootContent = newContent; + } + + function verifyImportedDiagnostics() { + const diags = project.getLanguageService().getSemanticDiagnostics(imported.path); + assert.equal(diags.length, 1); + const diag = diags[0]; + assert.equal(diag.code, Diagnostics.Cannot_find_name_0.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find name 'foo'."); + } + + function getLocationsForModuleLookup(module: string) { + const locations: string[] = []; + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.ts`), + combinePaths(ancestor, `${module}.tsx`), + combinePaths(ancestor, `${module}.d.ts`) + ); + }); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + locations.push( + combinePaths(ancestor, `${module}.js`), + combinePaths(ancestor, `${module}.jsx`) + ); + }); + return locations; + } + + function getLocationsForDirectoryLookup() { + const result = createMap(); + // Type root + result.set(typeRootFromTsserverLocation, 1); + forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { + // To resolve modules + result.set(ancestor, 2); + // for type roots + result.set(combinePaths(ancestor, `node_modules`), 1); + }); + return result; + } + }); + + it("loads missing files from disk", () => { + const root: FileOrFolder = { + path: "/c/foo.ts", + content: `import {y} from "bar"` + }; + + const imported: FileOrFolder = { + path: "/c/bar.d.ts", + content: `export var y = 1` + }; + + const host = createServerHost([root]); + const projectService = createProjectService(host); + projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); + const callsTrackingHost = createCallsTrackingHost(host); + projectService.openClientFile(root.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const project = projectService.inferredProjects[0]; + const rootScriptInfo = project.getRootScriptInfos()[0]; + assert.equal(rootScriptInfo.fileName, root.path); + + let diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 1); + const diag = diags[0]; + assert.equal(diag.code, Diagnostics.Cannot_find_module_0.code); + assert.equal(flattenDiagnosticMessageText(diag.messageText, "\n"), "Cannot find module 'bar'."); + callsTrackingHost.verifyCalledOn("fileExists", imported.path); + + + callsTrackingHost.clear(); + host.reloadFS([root, imported]); + host.runQueuedTimeoutCallbacks(); + diags = project.getLanguageService().getSemanticDiagnostics(root.path); + assert.equal(diags.length, 0); + callsTrackingHost.verifyCalledOn("fileExists", imported.path); + }); + it("when calling goto definition of module", () => { const clientFile: FileOrFolder = { path: "/a/b/controllers/vessels/client.ts", @@ -4371,18 +4523,7 @@ namespace ts.projectSystem { const canonicalFile3Path = useCaseSensitiveFileNames ? file3.path : file3.path.toLocaleLowerCase(); const numberOfTimesWatchInvoked = getNumberOfWatchesInvokedForRecursiveWatches(watchingRecursiveDirectories, canonicalFile3Path); callsTrackingHost.verifyCalledOnEachEntryNTimes("fileExists", [canonicalFile3Path], numberOfTimesWatchInvoked); - - // Called for type root resolution - const directoryExistsCalled = createMap(); - for (let dir = frontendDir; dir !== "/"; dir = getDirectoryPath(dir)) { - directoryExistsCalled.set(`${dir}/node_modules`, 2); - } - directoryExistsCalled.set(`/node_modules`, 2); - directoryExistsCalled.set(`${frontendDir}/types`, 2); - directoryExistsCalled.set(`${frontendDir}/node_modules/@types`, 2); - directoryExistsCalled.set(canonicalFile3Path, numberOfTimesWatchInvoked); - callsTrackingHost.verifyCalledOnEachEntry("directoryExists", directoryExistsCalled); - + callsTrackingHost.verifyCalledOnEachEntryNTimes("directoryExists", [canonicalFile3Path], numberOfTimesWatchInvoked); callsTrackingHost.verifyNoCall("getDirectories"); callsTrackingHost.verifyCalledOnEachEntryNTimes("readFile", [file3.path], 1); callsTrackingHost.verifyNoCall("readDirectory"); From 0ff160f93f14b4c04fd533d6d74bf14a86298b0b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Sep 2017 19:36:32 -0700 Subject: [PATCH 092/144] Add files to change set instead of delay reloading project on "change" command --- src/server/editorServices.ts | 17 +++++++++++------ src/server/session.ts | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 101f9fbb720..4b761288290 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1948,12 +1948,7 @@ namespace ts.server { const change = file.changes[i]; scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); } - if (!this.changedFiles) { - this.changedFiles = [scriptInfo]; - } - else if (!contains(this.changedFiles, scriptInfo)) { - this.changedFiles.push(scriptInfo); - } + this.addChangedFile(scriptInfo); } } @@ -1969,6 +1964,16 @@ namespace ts.server { } } + /* @internal */ + addChangedFile(scriptInfo: ScriptInfo) { + if (!this.changedFiles) { + this.changedFiles = [scriptInfo]; + } + else if (!contains(this.changedFiles, scriptInfo)) { + this.changedFiles.push(scriptInfo); + } + } + private closeConfiguredProject(configFile: NormalizedPath): void { const configuredProject = this.findConfiguredProjectByProjectName(configFile); if (configuredProject && configuredProject.deleteOpenRef() === 0) { diff --git a/src/server/session.ts b/src/server/session.ts index b4f2b46f99c..4b307d27970 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1287,9 +1287,9 @@ namespace ts.server { const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); if (start >= 0) { scriptInfo.editContent(start, end, args.insertString); + this.projectService.addChangedFile(scriptInfo); this.changeSeq++; } - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } } From e6eede13ef12882e53e10c146985f1ffb2148c28 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Sep 2017 13:18:58 -0700 Subject: [PATCH 093/144] Update how we get project/script info so that it doesnt start unnecessary update graph --- src/server/editorServices.ts | 25 +++-- src/server/session.ts | 193 +++++++++++++++++------------------ 2 files changed, 111 insertions(+), 107 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4b761288290..3114bb97e82 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -588,11 +588,16 @@ namespace ts.server { return this.findExternalProjectByProjectName(projectName) || this.findConfiguredProjectByProjectName(toNormalizedPath(projectName)); } - getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean) { - if (refreshInferredProjects) { + getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean) { + let scriptInfo = this.getScriptInfoForNormalizedPath(fileName); + if (ensureProject && !scriptInfo || scriptInfo.isOrphan()) { this.ensureProjectStructuresUptoDate(); + scriptInfo = this.getScriptInfoForNormalizedPath(fileName); + if (!scriptInfo) { + return Errors.ThrowNoProject(); + } + return scriptInfo.getDefaultProject(); } - const scriptInfo = this.getScriptInfoForNormalizedPath(fileName); return scriptInfo && !scriptInfo.isOrphan() && scriptInfo.getDefaultProject(); } @@ -1943,12 +1948,7 @@ namespace ts.server { for (const file of changedFiles) { const scriptInfo = this.getScriptInfo(file.fileName); Debug.assert(!!scriptInfo); - // apply changes in reverse order - for (let i = file.changes.length - 1; i >= 0; i--) { - const change = file.changes[i]; - scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); - } - this.addChangedFile(scriptInfo); + this.applyChangesToFile(scriptInfo, file.changes); } } @@ -1965,7 +1965,12 @@ namespace ts.server { } /* @internal */ - addChangedFile(scriptInfo: ScriptInfo) { + applyChangesToFile(scriptInfo: ScriptInfo, changes: TextChange[]) { + // apply changes in reverse order + for (let i = changes.length - 1; i >= 0; i--) { + const change = changes[i]; + scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText); + } if (!this.changedFiles) { this.changedFiles = [scriptInfo]; } diff --git a/src/server/session.ts b/src/server/session.ts index 4b307d27970..92f4b34a46e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -593,8 +593,7 @@ namespace ts.server { private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); const definitions = project.getLanguageService().getDefinitionAtPosition(file, position); if (!definitions) { @@ -618,8 +617,7 @@ namespace ts.server { private getTypeDefinition(args: protocol.FileLocationRequestArgs): ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); const definitions = project.getLanguageService().getTypeDefinitionAtPosition(file, position); if (!definitions) { @@ -638,7 +636,7 @@ namespace ts.server { private getImplementation(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); + const position = this.getPositionInFile(args, file); const implementations = project.getLanguageService().getImplementationAtPosition(file, position); if (!implementations) { return emptyArray; @@ -660,8 +658,7 @@ namespace ts.server { private getOccurrences(args: protocol.FileLocationRequestArgs): ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); const occurrences = project.getLanguageService().getOccurrencesAtPosition(file, position); @@ -708,8 +705,7 @@ namespace ts.server { private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): ReadonlyArray | ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); const documentHighlights = project.getLanguageService().getDocumentHighlights(file, position, args.filesToSearch); if (!documentHighlights) { @@ -750,7 +746,8 @@ namespace ts.server { } private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean, excludeConfigFiles: boolean) { - const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*refreshInferredProjects*/ true, /*errorOnMissingProject*/ true); + const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName); + project.updateGraph(); const projectInfo = { configFileName: project.getProjectName(), languageServiceDisabled: !project.languageServiceEnabled, @@ -761,8 +758,7 @@ namespace ts.server { private getRenameInfo(args: protocol.FileLocationRequestArgs) { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); return project.getLanguageService().getRenameInfo(file, position); } @@ -799,8 +795,7 @@ namespace ts.server { private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | ReadonlyArray { const file = toNormalizedPath(args.file); - const info = this.projectService.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, info); + const position = this.getPositionInFile(args, file); const projects = this.getProjects(args); if (simplifiedResult) { @@ -905,14 +900,13 @@ namespace ts.server { const projects = this.getProjects(args); const defaultProject = this.getDefaultProject(args); - const scriptInfo = defaultProject.getScriptInfoForNormalizedPath(file); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); if (simplifiedResult) { const nameInfo = defaultProject.getLanguageService().getQuickInfoAtPosition(file, position); if (!nameInfo) { return undefined; } - const displayString = displayPartsToString(nameInfo.displayParts); const nameSpan = nameInfo.textSpan; const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset; @@ -988,26 +982,38 @@ namespace ts.server { return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset); } - private getFileAndProject(args: protocol.FileRequestArgs, errorOnMissingProject = true) { - return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ true, errorOnMissingProject); + private getPositionInFile(args: protocol.FileLocationRequestArgs, file: NormalizedPath): number { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); + return this.getPosition(args, scriptInfo); } - private getFileAndProjectWithoutRefreshingInferredProjects(args: protocol.FileRequestArgs, errorOnMissingProject = true) { - return this.getFileAndProjectWorker(args.file, args.projectFileName, /*refreshInferredProjects*/ false, errorOnMissingProject); + private getFileAndProject(args: protocol.FileRequestArgs) { + return this.getFileAndProjectWorker(args.file, args.projectFileName); } - private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, refreshInferredProjects: boolean, errorOnMissingProject: boolean) { - const file = toNormalizedPath(uncheckedFileName); - const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, refreshInferredProjects); - if (!project && errorOnMissingProject) { + private getFileAndLanguageServiceForSyntacticOperation(args: protocol.FileRequestArgs) { + // Since this is syntactic operation, there should always be project for the file + // we wouldnt have to ensure project but rather throw if we dont get project + const file = toNormalizedPath(args.file); + const project = this.getProject(args.projectFileName) || this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ false); + if (!project) { return Errors.ThrowNoProject(); } + return { + file, + languageService: project.getLanguageService(/*ensureSynchronized*/ false) + }; + } + + private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string) { + const file = toNormalizedPath(uncheckedFileName); + const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ true); return { file, project }; } private getOutliningSpans(args: protocol.FileRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - return project.getLanguageService(/*ensureSynchronized*/ false).getOutliningSpans(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + return languageService.getOutliningSpans(file); } private getTodoComments(args: protocol.TodoCommentRequestArgs) { @@ -1016,49 +1022,47 @@ namespace ts.server { } private getDocCommentTemplate(args: protocol.FileLocationRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); - return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const position = this.getPositionInFile(args, file); + return languageService.getDocCommentTemplateAtPosition(file, position); } private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const onlyMultiLine = args.onlyMultiLine; - const position = this.getPosition(args, scriptInfo); - return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine); + const position = this.getPositionInFile(args, file); + return languageService.getSpanOfEnclosingComment(file, position, onlyMultiLine); } private getIndentation(args: protocol.IndentationRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const position = this.getPositionInFile(args, file); const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); - const indentation = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, options); + const indentation = languageService.getIndentationAtPosition(file, position, options); return { position, indentation }; } private getBreakpointStatement(args: protocol.FileLocationRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); - return project.getLanguageService(/*ensureSynchronized*/ false).getBreakpointStatementAtPosition(file, position); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const position = this.getPositionInFile(args, file); + return languageService.getBreakpointStatementAtPosition(file, position); } private getNameOrDottedNameSpan(args: protocol.FileLocationRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); - return project.getLanguageService(/*ensureSynchronized*/ false).getNameOrDottedNameSpan(file, position, position); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const position = this.getPositionInFile(args, file); + return languageService.getNameOrDottedNameSpan(file, position, position); } private isValidBraceCompletion(args: protocol.BraceCompletionRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file)); - return project.getLanguageService(/*ensureSynchronized*/ false).isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0)); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const position = this.getPositionInFile(args, file); + return languageService.isValidBraceCompletionAtPosition(file, position, args.openingBrace.charCodeAt(0)); } private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const quickInfo = project.getLanguageService().getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo)); if (!quickInfo) { return undefined; @@ -1084,14 +1088,14 @@ namespace ts.server { } private getFormattingEditsForRange(args: protocol.FormatRequestArgs): protocol.CodeEdit[] { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const startPosition = scriptInfo.lineOffsetToPosition(args.line, args.offset); const endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); // TODO: avoid duplicate code (with formatonkey) - const edits = project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, startPosition, endPosition, + const edits = languageService.getFormattingEditsForRange(file, startPosition, endPosition, this.projectService.getFormatCodeOptions(file)); if (!edits) { return undefined; @@ -1101,29 +1105,29 @@ namespace ts.server { } private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); - return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForRange(file, args.position, args.endPosition, options); + return languageService.getFormattingEditsForRange(file, args.position, args.endPosition, options); } private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); - return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsForDocument(file, options); + return languageService.getFormattingEditsForDocument(file, options); } private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const options = args.options ? convertFormatOptions(args.options) : this.projectService.getFormatCodeOptions(file); - return project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, args.position, args.key, options); + return languageService.getFormattingEditsAfterKeystroke(file, args.position, args.key, options); } private getFormattingEditsAfterKeystroke(args: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = scriptInfo.lineOffsetToPosition(args.line, args.offset); const formatOptions = this.projectService.getFormatCodeOptions(file); - const edits = project.getLanguageService(/*ensureSynchronized*/ false).getFormattingEditsAfterKeystroke(file, position, args.key, + const edits = languageService.getFormattingEditsAfterKeystroke(file, position, args.key, formatOptions); // Check whether we should auto-indent. This will be when // the position is on a line containing only whitespace. @@ -1134,7 +1138,7 @@ namespace ts.server { if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) { const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line); if (lineText && lineText.search("\\S") < 0) { - const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions); + const preferredIndent = languageService.getIndentationAtPosition(file, position, formatOptions); let hasIndent = 0; let i: number, len: number; for (i = 0, len = lineText.length; i < len; i++) { @@ -1175,8 +1179,7 @@ namespace ts.server { private getCompletions(args: protocol.CompletionsRequestArgs, simplifiedResult: boolean): ReadonlyArray | CompletionInfo | undefined { const prefix = args.prefix || ""; const { file, project } = this.getFileAndProject(args); - - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); const completions = project.getLanguageService().getCompletionsAtPosition(file, position); @@ -1196,8 +1199,7 @@ namespace ts.server { private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): ReadonlyArray { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const position = this.getPosition(args, scriptInfo); + const position = this.getPositionInFile(args, file); return mapDefined(args.entryNames, entryName => project.getLanguageService().getCompletionEntryDetails(file, position, entryName)); @@ -1239,7 +1241,7 @@ namespace ts.server { private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems { const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); const helpItems = project.getLanguageService().getSignatureHelpItems(file, position); if (!helpItems) { @@ -1267,7 +1269,7 @@ namespace ts.server { private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] { return mapDefined(fileNames, uncheckedFileName => { const fileName = toNormalizedPath(uncheckedFileName); - const project = defaultProject || this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true); + const project = defaultProject || this.projectService.getDefaultProjectForFile(fileName, /*ensureProject*/ false); return project && { fileName, project }; }); } @@ -1280,29 +1282,27 @@ namespace ts.server { } private change(args: protocol.ChangeRequestArgs) { - const { file, project } = this.getFileAndProject(args, /*errorOnMissingProject*/ false); - if (project) { - const scriptInfo = project.getScriptInfoForNormalizedPath(file); - const start = scriptInfo.lineOffsetToPosition(args.line, args.offset); - const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); - if (start >= 0) { - scriptInfo.editContent(start, end, args.insertString); - this.projectService.addChangedFile(scriptInfo); - this.changeSeq++; - } + const scriptInfo = this.projectService.getScriptInfo(args.file); + Debug.assert(!!scriptInfo); + const start = scriptInfo.lineOffsetToPosition(args.line, args.offset); + const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); + if (start >= 0) { + this.projectService.applyChangesToFile(scriptInfo, [{ + span: { start, length: end - start }, + newText: args.insertString + }]); + this.changeSeq++; } } private reload(args: protocol.ReloadRequestArgs, reqSeq: number) { const file = toNormalizedPath(args.file); const tempFileName = args.tmpfile && toNormalizedPath(args.tmpfile); - const project = this.projectService.getDefaultProjectForFile(file, /*refreshInferredProjects*/ true); - if (project) { - this.changeSeq++; - // make sure no changes happen before this one is finished - if (project.reloadScript(file, tempFileName)) { - this.output(undefined, CommandNames.Reload, reqSeq); - } + const project = this.projectService.getDefaultProjectForFile(file, /*ensureProject*/ true); + this.changeSeq++; + // make sure no changes happen before this one is finished + if (project.reloadScript(file, tempFileName)) { + this.output(undefined, CommandNames.Reload, reqSeq); } } @@ -1333,12 +1333,12 @@ namespace ts.server { } private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] { - const { file, project } = this.getFileAndProject(args); - const items = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationBarItems(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const items = languageService.getNavigationBarItems(file); return !items ? undefined : simplifiedResult - ? this.decorateNavigationBarItems(items, project.getScriptInfoForNormalizedPath(file)) + ? this.decorateNavigationBarItems(items, this.projectService.getScriptInfoForNormalizedPath(file)) : items; } @@ -1360,12 +1360,12 @@ namespace ts.server { } private getNavigationTree(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationTree | NavigationTree { - const { file, project } = this.getFileAndProject(args); - const tree = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationTree(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const tree = languageService.getNavigationTree(file); return !tree ? undefined : simplifiedResult - ? this.decorateNavigationTree(tree, project.getScriptInfoForNormalizedPath(file)) + ? this.decorateNavigationTree(tree, this.projectService.getScriptInfoForNormalizedPath(file)) : tree; } @@ -1475,14 +1475,14 @@ namespace ts.server { } private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file); const { position, textRange } = this.extractPositionAndRange(args, scriptInfo); return project.getLanguageService().getApplicableRefactors(file, position || textRange); } private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file); const { position, textRange } = this.extractPositionAndRange(args, scriptInfo); @@ -1522,7 +1522,7 @@ namespace ts.server { if (args.errorCodes.length === 0) { return undefined; } - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); + const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file); const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); @@ -1589,12 +1589,11 @@ namespace ts.server { } private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] { - const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args); - - const scriptInfo = project.getScriptInfoForNormalizedPath(file); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file); const position = this.getPosition(args, scriptInfo); - const spans = project.getLanguageService(/*ensureSynchronized*/ false).getBraceMatchingAtPosition(file, position); + const spans = languageService.getBraceMatchingAtPosition(file, position); return !spans ? undefined : simplifiedResult @@ -1617,7 +1616,7 @@ namespace ts.server { const lowPriorityFiles: NormalizedPath[] = []; const veryLowPriorityFiles: NormalizedPath[] = []; const normalizedFileName = toNormalizedPath(fileName); - const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*refreshInferredProjects*/ true); + const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*ensureProject*/ true); for (const fileNameInProject of fileNamesInProject) { if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) { highPriorityFiles.push(fileNameInProject); From 2a5d95448650953e4f978f064ef6e5607f16e925 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Sep 2017 13:29:24 -0700 Subject: [PATCH 094/144] Reduce the file size for npm install test --- src/harness/unittests/tsserverProjectSystem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7c04f08938d..3537fde9a49 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4651,7 +4651,7 @@ namespace ts.projectSystem { { "path": "/a/b/node_modules/.staging/rxjs-22375c61/bundles" }, { "path": "/a/b/node_modules/.staging/rxjs-22375c61/operator" }, { "path": "/a/b/node_modules/.staging/rxjs-22375c61/src/add/observable/dom" }, - { "path": "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", "content": "// Type definitions for Lo-Dash 4.14\n// Project: http://lodash.com/\n// Definitions by: Brian Zengel ,\n// Ilya Mochalov ,\n// Stepan Mikhaylyuk ,\n// Eric L Anderson ,\n// AJ Richardson ,\n// Junyoung Clare Jang \n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n// TypeScript Version: 2.2\n\n/**\n### 4.0.0 Changelog (https://github.com/lodash/lodash/wiki/Changelog)\n\n#### TODO:\nremoved:\n- [x] Removed _.support\n- [x] Removed _.findWhere in favor of _.find with iteratee shorthand\n- [x] Removed _.where in favor of _.filter with iteratee shorthand\n- [x] Removed _.pluck in favor of _.map with iteratee shorthand\n\nrenamed:\n- [x] Renamed _.first to _.head\n- [x] Renamed _.indexBy to _.keyBy\n- [x] Renamed _.invoke to _.invokeMap\n- [x] Renamed _.overArgs to _.overArgs\n- [x] Renamed _.padLeft & _.padRight to _.padStart & _.padEnd\n- [x] Renamed _.pairs to _.toPairs\n- [x] Renamed _.rest to _.tail\n- [x] Renamed _.restParam to _.rest\n- [x] Renamed _.sortByOrder to _.orderBy\n- [x] Renamed _.trimLeft & _.trimRight to _.trimStart & _.trimEnd\n- [x] Renamed _.trunc to _.truncate\n\nsplit:\n- [x] Split _.indexOf & _.lastIndexOf into _.sortedIndexOf & _.sortedLastIndexOf\n- [x] Split _.max & _.min into _.maxBy & _.minBy\n- [x] Split _.omit & _.pick into _.omitBy & _.pickBy\n- [x] Split _.sample into _.sampleSize\n- [x] Split _.sortedIndex into _.sortedIndexBy\n- [x] Split _.sortedLastIndex into _.sortedLastIndexBy\n- [x] Split _.uniq into _.sortedUniq, _.sortedUniqBy, & _.uniqBy\n\nchanges:\n- [x] Absorbed _.sortByAll into _.sortBy\n- [x] Changed the category of _.at to “Object”\n- [x] Changed the category of _.bindAll to “Utility”\n- [x] Made _.capitalize uppercase the first character & lowercase the rest\n- [x] Made _.functions return only own method names\n\nadded 23 array methods:\n- [x] _.concat\n- [x] _.differenceBy\n- [x] _.differenceWith\n- [x] _.flatMap\n- [x] _.fromPairs\n- [x] _.intersectionBy\n- [x] _.intersectionWith\n- [x] _.join\n- [x] _.pullAll\n- [x] _.pullAllBy\n- [x] _.reverse\n- [x] _.sortedIndexBy\n- [x] _.sortedIndexOf\n- [x] _.sortedLastIndexBy\n- [x] _.sortedLastIndexOf\n- [x] _.sortedUniq\n- [x] _.sortedUniqBy\n- [x] _.unionBy\n- [x] _.unionWith\n- [x] _.uniqBy\n- [x] _.uniqWith\n- [x] _.xorBy\n- [x] _.xorWith\n\nadded 20 lang methods:\n- [x] _.cloneDeepWith\n- [x] _.cloneWith\n- [x] _.eq\n- [x] _.isArrayLike\n- [x] _.isArrayLikeObject\n- [x] _.isEqualWith\n- [x] _.isInteger\n- [x] _.isLength\n- [x] _.isMatchWith\n- [x] _.isNil\n- [x] _.isObjectLike\n- [x] _.isSafeInteger\n- [x] _.isSymbol\n- [x] _.toInteger\n- [x] _.toLength\n- [x] _.toNumber\n- [x] _.toSafeInteger\n- [x] _.toString\n- [X] _.conforms\n- [X] _.conformsTo\n\nadded 13 object methods:\n- [x] _.assignIn\n- [x] _.assignInWith\n- [x] _.assignWith\n- [x] _.functionsIn\n- [x] _.hasIn\n- [x] _.mergeWith\n- [x] _.omitBy\n- [x] _.pickBy\n\nadded 8 string methods:\n- [x] _.lowerCase\n- [x] _.lowerFirst\n- [x] _.upperCase\n- [x] _.upperFirst\n- [x] _.toLower\n- [x] _.toUpper\n\nadded 8 utility methods:\n- [x] _.toPath\n\nadded 4 math methods:\n- [x] _.maxBy\n- [x] _.mean\n- [x] _.minBy\n- [x] _.sumBy\n\nadded 2 function methods:\n- [x] _.flip\n- [x] _.unary\n\nadded 2 number methods:\n- [x] _.clamp\n- [x] _.subtract\n\nadded collection method:\n- [x] _.sampleSize\n\nAdded 3 aliases\n\n- [x] _.first as an alias of _.head\n\nRemoved 17 aliases\n- [x] Removed aliase _.all\n- [x] Removed aliase _.any\n- [x] Removed aliase _.backflow\n- [x] Removed aliase _.callback\n- [x] Removed aliase _.collect\n- [x] Removed aliase _.compose\n- [x] Removed aliase _.contains\n- [x] Removed aliase _.detect\n- [x] Removed aliase _.foldl\n- [x] Removed aliase _.foldr\n- [x] Removed aliase _.include\n- [x] Removed aliase _.inject\n- [x] Removed aliase _.methods\n- [x] Removed aliase _.object\n- [x] Removed aliase _.run\n- [x] Removed aliase _.select\n- [x] Removed aliase _.unique\n\nOther changes\n- [x] Added support for array buffers to _.isEqual\n- [x] Added support for converting iterators to _.toArray\n- [x] Added support for deep paths to _.zipObject\n- [x] Changed UMD to export to window or self when available regardless of other exports\n- [x] Ensured debounce cancel clears args & thisArg references\n- [x] Ensured _.add, _.subtract, & _.sum don’t skip NaN values\n- [x] Ensured _.clone treats generators like functions\n- [x] Ensured _.clone produces clones with the source’s [[Prototype]]\n- [x] Ensured _.defaults assigns properties that shadow Object.prototype\n- [x] Ensured _.defaultsDeep doesn’t merge a string into an array\n- [x] Ensured _.defaultsDeep & _.merge don’t modify sources\n- [x] Ensured _.defaultsDeep works with circular references\n- [x] Ensured _.keys skips “length” on strict mode arguments objects in Safari 9\n- [x] Ensured _.merge doesn’t convert strings to arrays\n- [x] Ensured _.merge merges plain-objects onto non plain-objects\n- [x] Ensured _#plant resets iterator data of cloned sequences\n- [x] Ensured _.random swaps min & max if min is greater than max\n- [x] Ensured _.range preserves the sign of start of -0\n- [x] Ensured _.reduce & _.reduceRight use getIteratee in their array branch\n- [x] Fixed rounding issue with the precision param of _.floor\n- [x] Added flush method to debounced & throttled functions\n\n** LATER **\nMisc:\n- [ ] Made _.forEach, _.forIn, _.forOwn, & _.times implicitly end a chain sequence\n- [ ] Removed thisArg params from most methods\n- [ ] Made “By” methods provide a single param to iteratees\n- [ ] Made _.words chainable by default\n- [ ] Removed isDeep params from _.clone & _.flatten\n- [ ] Removed _.bindAll support for binding all methods when no names are provided\n- [ ] Removed func-first param signature from _.before & _.after\n- [ ] _.extend as an alias of _.assignIn\n- [ ] _.extendWith as an alias of _.assignInWith\n- [ ] Added clear method to _.memoize.Cache\n- [ ] Added support for ES6 maps, sets, & symbols to _.clone, _.isEqual, & _.toArray\n- [x] Enabled _.flow & _.flowRight to accept an array of functions\n- [ ] Ensured “Collection” methods treat functions as objects\n- [ ] Ensured _.assign, _.defaults, & _.merge coerce object values to objects\n- [ ] Ensured _.bindKey bound functions call object[key] when called with the new operator\n- [ ] Ensured _.isFunction returns true for generator functions\n- [ ] Ensured _.merge assigns typed arrays directly\n- [ ] Made _(...) an iterator & iterable\n- [ ] Made _.drop, _.take, & right forms coerce n of undefined to 0\n\nMethods:\n- [ ] _.concat\n- [ ] _.differenceBy\n- [ ] _.differenceWith\n- [ ] _.flatMap\n- [ ] _.fromPairs\n- [ ] _.intersectionBy\n- [ ] _.intersectionWith\n- [ ] _.join\n- [ ] _.pullAll\n- [ ] _.pullAllBy\n- [ ] _.reverse\n- [ ] _.sortedLastIndexOf\n- [ ] _.unionBy\n- [ ] _.unionWith\n- [ ] _.uniqWith\n- [ ] _.xorBy\n- [ ] _.xorWith\n- [ ] _.toString\n\n- [ ] _.invoke\n- [ ] _.setWith\n- [ ] _.toPairs\n- [ ] _.toPairsIn\n- [ ] _.unset\n\n- [ ] _.replace\n- [ ] _.split\n\n- [ ] _.cond\n- [ ] _.nthArg\n- [ ] _.over\n- [ ] _.overEvery\n- [ ] _.overSome\n- [ ] _.rangeRight\n\n- [ ] _.next\n*/\n\nexport = _;\nexport as namespace _;\n\ndeclare var _: _.LoDashStatic;\n\ntype PartialObject = Partial;\n\ndeclare namespace _ {\n type Many = T | T[];\n\n interface LoDashStatic {\n /**\n * Creates a lodash object which wraps the given value to enable intuitive method chaining.\n *\n * In addition to Lo-Dash methods, wrappers also have the following Array methods:\n * concat, join, pop, push, reverse, shift, slice, sort, splice, and unshift\n *\n * Chaining is supported in custom builds as long as the value method is implicitly or\n * explicitly included in the build.\n *\n * The chainable wrapper functions are:\n * after, assign, bind, bindAll, bindKey, chain, chunk, compact, compose, concat, countBy,\n * createCallback, curry, debounce, defaults, defer, delay, difference, filter, flatten,\n * forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, functions, groupBy,\n * keyBy, initial, intersection, invert, invoke, keys, map, max, memoize, merge, min,\n * object, omit, once, pairs, partial, partialRight, pick, pluck, pull, push, range, reject,\n * remove, rest, reverse, sample, shuffle, slice, sort, sortBy, splice, tap, throttle, times,\n * toArray, transform, union, uniq, unset, unshift, unzip, values, where, without, wrap, and zip\n *\n * The non-chainable wrapper functions are:\n * clone, cloneDeep, contains, escape, every, find, findIndex, findKey, findLast,\n * findLastIndex, findLastKey, has, identity, indexOf, isArguments, isArray, isBoolean,\n * isDate, isElement, isEmpty, isEqual, isFinite, isFunction, isNaN, isNull, isNumber,\n * isObject, isPlainObject, isRegExp, isString, isUndefined, join, lastIndexOf, mixin,\n * noConflict, parseInt, pop, random, reduce, reduceRight, result, shift, size, some,\n * sortedIndex, runInContext, template, unescape, uniqueId, and value\n *\n * The wrapper functions first and last return wrapped values when n is provided, otherwise\n * they return unwrapped values.\n *\n * Explicit chaining can be enabled by using the _.chain method.\n **/\n (value: number): LoDashImplicitWrapper;\n (value: string): LoDashImplicitStringWrapper;\n (value: boolean): LoDashImplicitWrapper;\n (value: null | undefined): LoDashImplicitWrapper;\n (value: number[]): LoDashImplicitNumberArrayWrapper;\n (value: T[]): LoDashImplicitArrayWrapper;\n (value: T[] | null | undefined): LoDashImplicitNillableArrayWrapper;\n (value: T): LoDashImplicitObjectWrapper;\n (value: T | null | undefined): LoDashImplicitNillableObjectWrapper;\n (value: any): LoDashImplicitWrapper;\n\n /**\n * The semantic version number.\n **/\n VERSION: string;\n\n /**\n * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby\n * (ERB). Change the following template settings to use alternative delimiters.\n **/\n templateSettings: TemplateSettings;\n }\n\n /**\n * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby\n * (ERB). Change the following template settings to use alternative delimiters.\n **/\n interface TemplateSettings {\n /**\n * The \"escape\" delimiter.\n **/\n escape?: RegExp;\n\n /**\n * The \"evaluate\" delimiter.\n **/\n evaluate?: RegExp;\n\n /**\n * An object to import into the template as local variables.\n **/\n imports?: Dictionary;\n\n /**\n * The \"interpolate\" delimiter.\n **/\n interpolate?: RegExp;\n\n /**\n * Used to reference the data object in the template text.\n **/\n variable?: string;\n }\n\n /**\n * Creates a cache object to store key/value pairs.\n */\n interface MapCache {\n /**\n * Removes `key` and its value from the cache.\n * @param key The key of the value to remove.\n * @return Returns `true` if the entry was removed successfully, else `false`.\n */\n delete(key: string): boolean;\n\n /**\n * Gets the cached value for `key`.\n * @param key The key of the value to get.\n * @return Returns the cached value.\n */\n get(key: string): any;\n\n /**\n * Checks if a cached value for `key` exists.\n * @param key The key of the entry to check.\n * @return Returns `true` if an entry for `key` exists, else `false`.\n */\n has(key: string): boolean;\n\n /**\n * Sets `value` to `key` of the cache.\n * @param key The key of the value to cache.\n * @param value The value to cache.\n * @return Returns the cache object.\n */\n set(key: string, value: any): _.Dictionary;\n\n /**\n * Removes all key-value entries from the map.\n */\n clear(): void;\n }\n interface MapCacheConstructor {\n new (): MapCache;\n }\n\n interface LoDashWrapperBase { }\n\n interface LoDashImplicitWrapperBase extends LoDashWrapperBase { }\n\n interface LoDashExplicitWrapperBase extends LoDashWrapperBase { }\n\n interface LoDashImplicitWrapper extends LoDashImplicitWrapperBase> { }\n\n interface LoDashExplicitWrapper extends LoDashExplicitWrapperBase> { }\n\n interface LoDashImplicitStringWrapper extends LoDashImplicitWrapper { }\n\n interface LoDashExplicitStringWrapper extends LoDashExplicitWrapper { }\n\n interface LoDashImplicitObjectWrapperBase extends LoDashImplicitWrapperBase { }\n\n interface LoDashImplicitObjectWrapper extends LoDashImplicitObjectWrapperBase> { }\n\n interface LoDashImplicitNillableObjectWrapper extends LoDashImplicitObjectWrapperBase> { }\n\n interface LoDashExplicitObjectWrapperBase extends LoDashExplicitWrapperBase { }\n\n interface LoDashExplicitObjectWrapper extends LoDashExplicitObjectWrapperBase> { }\n\n interface LoDashExplicitNillableObjectWrapper extends LoDashExplicitObjectWrapperBase> { }\n\n interface LoDashImplicitArrayWrapperBase extends LoDashImplicitWrapperBase {\n pop(): T | undefined;\n push(...items: T[]): TWrapper;\n shift(): T | undefined;\n sort(compareFn?: (a: T, b: T) => number): TWrapper;\n splice(start: number): TWrapper;\n splice(start: number, deleteCount: number, ...items: T[]): TWrapper;\n unshift(...items: T[]): TWrapper;\n }\n\n interface LoDashImplicitArrayWrapper extends LoDashImplicitArrayWrapperBase> { }\n\n interface LoDashImplicitNillableArrayWrapper extends LoDashImplicitArrayWrapperBase> { }\n\n interface LoDashImplicitNumberArrayWrapperBase extends LoDashImplicitArrayWrapperBase { }\n\n interface LoDashImplicitNumberArrayWrapper extends LoDashImplicitArrayWrapper { }\n\n interface LoDashExplicitArrayWrapperBase extends LoDashExplicitWrapperBase {\n pop(): LoDashExplicitObjectWrapper;\n push(...items: T[]): TWrapper;\n shift(): LoDashExplicitObjectWrapper;\n sort(compareFn?: (a: T, b: T) => number): TWrapper;\n splice(start: number): TWrapper;\n splice(start: number, deleteCount: number, ...items: T[]): TWrapper;\n unshift(...items: T[]): TWrapper;\n }\n\n interface LoDashExplicitArrayWrapper extends LoDashExplicitArrayWrapperBase> { }\n\n interface LoDashExplicitNillableArrayWrapper extends LoDashExplicitArrayWrapperBase> { }\n\n interface LoDashExplicitNumberArrayWrapper extends LoDashExplicitArrayWrapper { }\n\n /*********\n * Array *\n *********/\n\n //_.chunk\n interface LoDashStatic {\n /**\n * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the\n * final chunk will be the remaining elements.\n *\n * @param array The array to process.\n * @param size The length of each chunk.\n * @return Returns the new array containing chunks.\n */\n chunk(\n array: List | null | undefined,\n size?: number\n ): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.chunk\n */\n chunk(size?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.compact\n interface LoDashStatic {\n /**\n * Creates an array with all falsey values removed. The values false, null, 0, \"\", undefined, and NaN are\n * falsey.\n *\n * @param array The array to compact.\n * @return (Array) Returns the new array of filtered values.\n */\n compact(array?: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.compact\n */\n compact(): LoDashExplicitArrayWrapper;\n }\n\n //_.concat DUMMY\n interface LoDashStatic {\n /**\n * Creates a new array concatenating `array` with any additional arrays\n * and/or values.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to concatenate.\n * @param {...*} [values] The values to concatenate.\n * @returns {Array} Returns the new concatenated array.\n * @example\n *\n * var array = [1];\n * var other = _.concat(array, 2, [3], [[4]]);\n *\n * console.log(other);\n * // => [1, 2, 3, [4]]\n *\n * console.log(array);\n * // => [1]\n */\n concat(array: List, ...values: Array>): T[];\n }\n\n //_.difference\n interface LoDashStatic {\n /**\n * Creates an array of unique array values not included in the other provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param array The array to inspect.\n * @param values The arrays of values to exclude.\n * @return Returns the new array of filtered values.\n */\n difference(\n array: List | null | undefined,\n ...values: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.difference\n */\n difference(...values: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.differenceBy\n interface LoDashStatic {\n /**\n * This method is like _.difference except that it accepts iteratee which is invoked for each element of array\n * and values to generate the criterion by which uniqueness is computed. The iteratee is invoked with one\n * argument: (value).\n *\n * @param array The array to inspect.\n * @param values The values to exclude.\n * @param iteratee The iteratee invoked per element.\n * @returns Returns the new array of filtered values.\n */\n differenceBy(\n array: List | null | undefined,\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n array: List | null | undefined,\n ...values: any[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: ((value: T) => any)|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n values1?: List,\n values2?: List,\n values3?: List,\n values4?: List,\n values5?: List,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.differenceBy\n */\n differenceBy(\n ...values: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.differenceWith DUMMY\n interface LoDashStatic {\n /**\n * Creates an array of unique `array` values not included in the other\n * provided arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {...Array} [values] The values to exclude.\n * @returns {Array} Returns the new array of filtered values.\n * @example\n *\n * _.difference([3, 2, 1], [4, 2]);\n * // => [3, 1]\n */\n differenceWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.drop\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements dropped from the beginning.\n *\n * @param array The array to query.\n * @param n The number of elements to drop.\n * @return Returns the slice of array.\n */\n drop(array: List | null | undefined, n?: number): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.drop\n */\n drop(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.dropRight\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements dropped from the end.\n *\n * @param array The array to query.\n * @param n The number of elements to drop.\n * @return Returns the slice of array.\n */\n dropRight(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropRight\n */\n dropRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.dropRightWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array excluding elements dropped from the end. Elements are dropped until predicate\n * returns falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * match the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropRightWhile\n */\n dropRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.dropWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array excluding elements dropped from the beginning. Elements are dropped until predicate\n * returns falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.dropWhile\n */\n dropWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.fill\n interface LoDashStatic {\n /**\n * Fills elements of array with value from start up to, but not including, end.\n *\n * Note: This method mutates array.\n *\n * @param array The array to fill.\n * @param value The value to fill array with.\n * @param start The start position.\n * @param end The end position.\n * @return Returns array.\n */\n fill(\n array: any[] | null | undefined,\n value: T,\n start?: number,\n end?: number\n ): T[];\n\n /**\n * @see _.fill\n */\n fill(\n array: List | null | undefined,\n value: T,\n start?: number,\n end?: number\n ): List;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.fill\n */\n fill(\n value: T,\n start?: number,\n end?: number\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.findIndex\n interface LoDashStatic {\n /**\n * This method is like _.find except that it returns the index of the first element predicate returns truthy\n * for instead of the element itself.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the index of the found element, else -1.\n */\n findIndex(\n array: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n array: List | null | undefined,\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n array: List | null | undefined,\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findIndex\n */\n findIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.findLastIndex\n interface LoDashStatic {\n /**\n * This method is like _.findIndex except that it iterates over elements of collection from right to left.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the index of the found element, else -1.\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n array: List | null | undefined,\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): number;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: ListIterator,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastIndex\n */\n findLastIndex(\n predicate?: W,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.first\n interface LoDashStatic {\n /**\n * @see _.head\n */\n first(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.head\n */\n first(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n first(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n first(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.head\n */\n first(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n first(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n first(): T;\n }\n\n interface RecursiveArray extends Array> {}\n interface ListOfRecursiveArraysOrValues extends List> {}\n\n //_.flatten\n interface LoDashStatic {\n /**\n * Flattens a nested array. If isDeep is true the array is recursively flattened, otherwise it’s only\n * flattened a single level.\n *\n * @param array The array to flatten.\n * @param isDeep Specify a deep flatten.\n * @return Returns the new flattened array.\n */\n flatten(array: ListOfRecursiveArraysOrValues | null | undefined, isDeep: boolean): T[];\n\n /**\n * @see _.flatten\n */\n flatten(array: List> | null | undefined): T[];\n\n /**\n * @see _.flatten\n */\n flatten(array: ListOfRecursiveArraysOrValues | null | undefined): RecursiveArray;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flatten\n */\n flatten(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flatten\n */\n flatten(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flatten\n */\n flatten(isDeep?: boolean): LoDashExplicitArrayWrapper;\n }\n\n //_.flattenDeep\n interface LoDashStatic {\n /**\n * Recursively flattens a nested array.\n *\n * @param array The array to recursively flatten.\n * @return Returns the new flattened array.\n */\n flattenDeep(array: ListOfRecursiveArraysOrValues | null | undefined): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flattenDeep\n */\n flattenDeep(): LoDashExplicitArrayWrapper;\n }\n\n // _.flattenDepth\n interface LoDashStatic {\n /**\n * Recursively flatten array up to depth times.\n *\n * @param array The array to recursively flatten.\n * @param number The maximum recursion depth.\n * @return Returns the new flattened array.\n */\n flattenDepth(array: ListOfRecursiveArraysOrValues | null | undefined, depth?: number): T[];\n }\n\n //_.fromPairs\n interface LoDashStatic {\n /**\n * The inverse of `_.toPairs`; this method returns an object composed\n * from key-value `pairs`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} pairs The key-value pairs.\n * @returns {Object} Returns the new object.\n * @example\n *\n * _.fromPairs([['fred', 30], ['barney', 40]]);\n * // => { 'fred': 30, 'barney': 40 }\n */\n fromPairs(\n array: List<[_.StringRepresentable, T]> | null | undefined\n ): Dictionary;\n\n /**\n @see _.fromPairs\n */\n fromPairs(\n array: List | null | undefined\n ): Dictionary;\n }\n\n //_.fromPairs DUMMY\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.fromPairs\n */\n fromPairs(): LoDashImplicitObjectWrapper;\n }\n\n //_.fromPairs DUMMY\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.fromPairs\n */\n fromPairs(): LoDashExplicitObjectWrapper;\n }\n\n //_.head\n interface LoDashStatic {\n /**\n * Gets the first element of array.\n *\n * @alias _.first\n *\n * @param array The array to query.\n * @return Returns the first element of array.\n */\n head(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.head\n */\n head(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n head(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n head(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.head\n */\n head(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.head\n */\n head(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.head\n */\n head(): T;\n }\n\n //_.indexOf\n interface LoDashStatic {\n /**\n * Gets the index at which the first occurrence of `value` is found in `array`\n * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons. If `fromIndex` is negative, it's used as the offset\n * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`\n * performs a faster binary search.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @param {number} [fromIndex=0] The index to search from.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.indexOf([1, 2, 1, 2], 2);\n * // => 1\n *\n * // using `fromIndex`\n * _.indexOf([1, 2, 1, 2], 2, 2);\n * // => 3\n */\n indexOf(\n array: List | null | undefined,\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: TValue,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: T,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.indexOf\n */\n indexOf(\n value: TValue,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n //_.intersectionBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.intersection` except that it accepts `iteratee`\n * which is invoked for each element of each `arrays` to generate the criterion\n * by which uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new array of shared values.\n * @example\n *\n * _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);\n * // => [2.1]\n *\n * // using the `_.property` iteratee shorthand\n * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 1 }]\n */\n intersectionBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.intersectionWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.intersection` except that it accepts `comparator`\n * which is invoked to compare elements of `arrays`. The comparator is invoked\n * with two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of shared values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.intersectionWith(objects, others, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }]\n */\n intersectionWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.join\n interface LoDashStatic {\n /**\n * Converts all elements in `array` into a string separated by `separator`.\n *\n * @param array The array to convert.\n * @param separator The element separator.\n * @returns Returns the joined string.\n */\n join(\n array: List | null | undefined,\n separator?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.join\n */\n join(separator?: string): LoDashExplicitWrapper;\n }\n\n //_.pullAll DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.pull` except that it accepts an array of values to remove.\n *\n * **Note:** Unlike `_.difference`, this method mutates `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to modify.\n * @param {Array} values The values to remove.\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [1, 2, 3, 1, 2, 3];\n *\n * _.pull(array, [2, 3]);\n * console.log(array);\n * // => [1, 1]\n */\n pullAll(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.pullAllBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.pullAll` except that it accepts `iteratee` which is\n * invoked for each element of `array` and `values` to to generate the criterion\n * by which uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * **Note:** Unlike `_.differenceBy`, this method mutates `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to modify.\n * @param {Array} values The values to remove.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];\n *\n * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');\n * console.log(array);\n * // => [{ 'x': 2 }]\n */\n pullAllBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.reverse DUMMY\n interface LoDashStatic {\n /**\n * Reverses `array` so that the first element becomes the last, the second\n * element becomes the second to last, and so on.\n *\n * **Note:** This method mutates `array` and is based on\n * [`Array#reverse`](https://mdn.io/Array/reverse).\n *\n * @memberOf _\n * @category Array\n * @returns {Array} Returns `array`.\n * @example\n *\n * var array = [1, 2, 3];\n *\n * _.reverse(array);\n * // => [3, 2, 1]\n *\n * console.log(array);\n * // => [3, 2, 1]\n */\n reverse(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.sortedIndexOf\n interface LoDashStatic {\n /**\n * This method is like `_.indexOf` except that it performs a binary\n * search on a sorted `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.sortedIndexOf([1, 1, 2, 2], 2);\n * // => 2\n */\n sortedIndexOf(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: TValue\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexOf\n */\n sortedIndexOf(\n value: TValue\n ): LoDashExplicitWrapper;\n }\n\n //_.initial\n interface LoDashStatic {\n /**\n * Gets all but the last element of array.\n *\n * @param array The array to query.\n * @return Returns the slice of array.\n */\n initial(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.initial\n */\n initial(): LoDashExplicitArrayWrapper;\n }\n\n //_.intersection\n interface LoDashStatic {\n /**\n * Creates an array of unique values that are included in all of the provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of shared values.\n */\n intersection(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.intersection\n */\n intersection(...arrays: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.last\n interface LoDashStatic {\n /**\n * Gets the last element of array.\n *\n * @param array The array to query.\n * @return Returns the last element of array.\n */\n last(array: List | null | undefined): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.last\n */\n last(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.last\n */\n last(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.last\n */\n last(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.last\n */\n last(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.last\n */\n last(): T;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.last\n */\n last(): T;\n }\n\n //_.lastIndexOf\n interface LoDashStatic {\n /**\n * This method is like _.indexOf except that it iterates over elements of array from right to left.\n *\n * @param array The array to search.\n * @param value The value to search for.\n * @param fromIndex The index to search from or true to perform a binary search on a sorted array.\n * @return Returns the index of the matched value, else -1.\n */\n lastIndexOf(\n array: List | null | undefined,\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: T,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: TResult,\n fromIndex?: boolean|number\n ): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: T,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.lastIndexOf\n */\n lastIndexOf(\n value: TResult,\n fromIndex?: boolean|number\n ): LoDashExplicitWrapper;\n }\n\n //_.nth\n interface LoDashStatic {\n /**\n * Gets the element at index `n` of `array`. If `n` is negative, the nth element from the end is returned.\n *\n * @param array array The array to query.\n * @param value The index of the element to return.\n * @return Returns the nth element of `array`.\n */\n nth(\n array: List | null | undefined,\n n?: number\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?: number\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): TResult | undefined;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.nth\n */\n nth(\n n?:number\n ): LoDashExplicitWrapper;\n }\n\n //_.pull\n interface LoDashStatic {\n /**\n * Removes all provided values from array using SameValueZero for equality comparisons.\n *\n * Note: Unlike _.without, this method mutates array.\n *\n * @param array The array to modify.\n * @param values The values to remove.\n * @return Returns array.\n */\n pull(\n array: T[],\n ...values: T[]\n ): T[];\n\n /**\n * @see _.pull\n */\n pull(\n array: List,\n ...values: T[]\n ): List;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.pull\n */\n pull(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.pull\n */\n pull(...values: TValue[]): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.pull\n */\n pull(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.pull\n */\n pull(...values: TValue[]): LoDashExplicitObjectWrapper>;\n }\n\n //_.pullAt\n interface LoDashStatic {\n /**\n * Removes elements from array corresponding to the given indexes and returns an array of the removed elements.\n * Indexes may be specified as an array of indexes or as individual arguments.\n *\n * Note: Unlike _.at, this method mutates array.\n *\n * @param array The array to modify.\n * @param indexes The indexes of elements to remove, specified as individual indexes or arrays of indexes.\n * @return Returns the new array of removed elements.\n */\n pullAt(\n array: List,\n ...indexes: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.pullAt\n */\n pullAt(...indexes: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.remove\n interface LoDashStatic {\n /**\n * Removes all elements from array that predicate returns truthy for and returns an array of the removed\n * elements. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * Note: Unlike _.filter, this method mutates array.\n *\n * @param array The array to modify.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new array of removed elements.\n */\n remove(\n array: List,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.remove\n */\n remove(\n array: List,\n predicate?: string\n ): T[];\n\n /**\n * @see _.remove\n */\n remove(\n array: List,\n predicate?: W\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.remove\n */\n remove(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.remove\n */\n remove(\n predicate?: W\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.tail\n interface LoDashStatic {\n /**\n * Gets all but the first element of array.\n *\n * @alias _.tail\n *\n * @param array The array to query.\n * @return Returns the slice of array.\n */\n tail(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.tail\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n //_.slice\n interface LoDashStatic {\n /**\n * Creates a slice of array from start up to, but not including, end.\n *\n * @param array The array to slice.\n * @param start The start position.\n * @param end The end position.\n * @return Returns the slice of array.\n */\n slice(\n array: T[] | null | undefined,\n start?: number,\n end?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.slice\n */\n slice(\n start?: number,\n end?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.slice\n */\n slice(\n start?: number,\n end?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedIndex\n interface LoDashStatic {\n /**\n * Uses a binary search to determine the lowest index at which `value` should\n * be inserted into `array` in order to maintain its sort order.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * _.sortedIndex([30, 50], 40);\n * // => 1\n *\n * _.sortedIndex([4, 5], 4);\n * // => 0\n */\n sortedIndex(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndex\n */\n sortedIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n // _.sortedIndexBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortedIndex` except that it accepts `iteratee`\n * which is invoked for `value` and each element of `array` to compute their\n * sort ranking. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };\n *\n * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));\n * // => 1\n *\n * // using the `_.property` iteratee shorthand\n * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');\n * // => 0\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedIndexBy\n */\n sortedIndexBy(\n value: T,\n iteratee: Object\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndex\n interface LoDashStatic {\n /**\n * This method is like `_.sortedIndex` except that it returns the highest\n * index at which `value` should be inserted into `array` in order to\n * maintain its sort order.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * _.sortedLastIndex([4, 5], 4);\n * // => 1\n */\n sortedLastIndex(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndex\n */\n sortedLastIndex(\n value: T\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndexBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortedLastIndex` except that it accepts `iteratee`\n * which is invoked for `value` and each element of `array` to compute their\n * sort ranking. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The sorted array to inspect.\n * @param {*} value The value to evaluate.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the index at which `value` should be inserted into `array`.\n * @example\n *\n * // using the `_.property` iteratee shorthand\n * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');\n * // => 1\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n array: List | null | undefined,\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): number;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: Object\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: string,\n iteratee: (x: string) => TSort\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => TSort\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: (x: T) => any\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: W\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sortedLastIndexBy\n */\n sortedLastIndexBy(\n value: T,\n iteratee: Object\n ): LoDashExplicitWrapper;\n }\n\n //_.sortedLastIndexOf DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.lastIndexOf` except that it performs a binary\n * search on a sorted `array`.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to search.\n * @param {*} value The value to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n * @example\n *\n * _.sortedLastIndexOf([1, 1, 2, 2], 2);\n * // => 3\n */\n sortedLastIndexOf(\n array: List | null | undefined,\n value: T\n ): number;\n }\n\n //_.tail\n interface LoDashStatic {\n /**\n * @see _.rest\n */\n tail(array: List | null | undefined): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.rest\n */\n tail(): LoDashExplicitArrayWrapper;\n }\n\n //_.take\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements taken from the beginning.\n *\n * @param array The array to query.\n * @param n The number of elements to take.\n * @return Returns the slice of array.\n */\n take(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.take\n */\n take(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.takeRight\n interface LoDashStatic {\n /**\n * Creates a slice of array with n elements taken from the end.\n *\n * @param array The array to query.\n * @param n The number of elements to take.\n * @return Returns the slice of array.\n */\n takeRight(\n array: List | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeRight\n */\n takeRight(n?: number): LoDashExplicitArrayWrapper;\n }\n\n //_.takeRightWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array with elements taken from the end. Elements are taken until predicate returns\n * falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeRightWhile\n */\n takeRightWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.takeWhile\n interface LoDashStatic {\n /**\n * Creates a slice of array with elements taken from the beginning. Elements are taken until predicate returns\n * falsey. The predicate is bound to thisArg and invoked with three arguments: (value, index, array).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param array The array to query.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the slice of array.\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: ListIterator\n ): TValue[];\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: string\n ): TValue[];\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n array: List | null | undefined,\n predicate?: TWhere\n ): TValue[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.takeWhile\n */\n takeWhile(\n predicate?: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.union\n interface LoDashStatic {\n /**\n * Creates an array of unique values, in order, from all of the provided arrays using SameValueZero for\n * equality comparisons.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of combined values.\n */\n union(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.union\n */\n union(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n //_.unionBy\n interface LoDashStatic {\n /**\n * This method is like `_.union` except that it accepts `iteratee` which is\n * invoked for each element of each `arrays` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @param arrays The arrays to inspect.\n * @param iteratee The iteratee invoked per element.\n * @return Returns the new array of combined values.\n */\n unionBy(\n arrays: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays1: List | null | undefined,\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): T[];\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays: List | null | undefined,\n ...iteratee: any[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: (value: T) => any\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n arrays2: List | null | undefined,\n arrays3: List | null | undefined,\n arrays4: List | null | undefined,\n arrays5: List | null | undefined,\n iteratee?: W\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.unionBy\n */\n unionBy(\n ...iteratee: any[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.uniq\n interface LoDashStatic {\n /**\n * Creates a duplicate-free version of an array, using\n * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * for equality comparisons, in which only the first occurrence of each element\n * is kept.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.uniq([2, 1, 2]);\n * // => [2, 1]\n */\n uniq(\n array: List | null | undefined\n ): T[];\n\n /**\n * @see _.uniq\n */\n uniq(\n array: List | null | undefined\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n uniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniq\n */\n uniq(): LoDashExplicitArrayWrapper;\n }\n\n //_.uniqBy\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.uniqBy([2.1, 1.2, 2.3], Math.floor);\n * // => [2.1, 1.2]\n *\n * // using the `_.property` iteratee shorthand\n * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 1 }, { 'x': 2 }]\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: Object\n ): T[];\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n array: List | null | undefined,\n iteratee: TWhere\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: Object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: Object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.uniqBy\n */\n uniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedUniq\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it's designed and optimized\n * for sorted arrays.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.sortedUniq([1, 1, 2]);\n * // => [1, 2]\n */\n sortedUniq(\n array: List | null | undefined\n ): T[];\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(\n array: List | null | undefined\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n sortedUniq(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniq\n */\n sortedUniq(): LoDashExplicitArrayWrapper;\n }\n\n //_.sortedUniqBy\n interface LoDashStatic {\n /**\n * This method is like `_.uniqBy` except that it's designed and optimized\n * for sorted arrays.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function} [iteratee] The iteratee invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);\n * // => [1.1, 2.2]\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: ListIterator\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: Object\n ): T[];\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n array: List | null | undefined,\n iteratee: TWhere\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: Object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: Object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortedUniqBy\n */\n sortedUniqBy(\n iteratee: TWhere\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.unionWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.union` except that it accepts `comparator` which\n * is invoked to compare elements of `arrays`. The comparator is invoked\n * with two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of combined values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.unionWith(objects, others, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]\n */\n unionWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.uniqWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.uniq` except that it accepts `comparator` which\n * is invoked to compare elements of `array`. The comparator is invoked with\n * two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new duplicate free array.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.uniqWith(objects, _.isEqual);\n * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]\n */\n uniqWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.unzip\n interface LoDashStatic {\n /**\n * This method is like _.zip except that it accepts an array of grouped elements and creates an array\n * regrouping the elements to their pre-zip configuration.\n *\n * @param array The array of grouped elements to process.\n * @return Returns the new array of regrouped elements.\n */\n unzip(array: List> | null | undefined): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.unzip\n */\n unzip(): LoDashExplicitArrayWrapper;\n }\n\n //_.unzipWith\n interface LoDashStatic {\n /**\n * This method is like _.unzip except that it accepts an iteratee to specify how regrouped values should be\n * combined. The iteratee is bound to thisArg and invoked with four arguments: (accumulator, value, index,\n * group).\n *\n * @param array The array of grouped elements to process.\n * @param iteratee The function to combine regrouped values.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new array of regrouped elements.\n */\n unzipWith(\n array: List> | null | undefined,\n iteratee?: MemoIterator\n ): TResult[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.unzipWith\n */\n unzipWith(\n iteratee?: MemoIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.unzipWith\n */\n unzipWith(\n iteratee?: MemoIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n //_.without\n interface LoDashStatic {\n /**\n * Creates an array excluding all provided values using SameValueZero for equality comparisons.\n *\n * @param array The array to filter.\n * @param values The values to exclude.\n * @return Returns the new array of filtered values.\n */\n without(\n array: List | null | undefined,\n ...values: T[]\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.without\n */\n without(...values: T[]): LoDashExplicitArrayWrapper;\n }\n\n //_.xor\n interface LoDashStatic {\n /**\n * Creates an array of unique values that is the symmetric difference of the provided arrays.\n *\n * @param arrays The arrays to inspect.\n * @return Returns the new array of values.\n */\n xor(...arrays: Array | null | undefined>): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.xor\n */\n xor(...arrays: Array | null | undefined>): LoDashExplicitArrayWrapper;\n }\n\n //_.xorBy DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.xor` except that it accepts `iteratee` which is\n * invoked for each element of each `arrays` to generate the criterion by which\n * uniqueness is computed. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new array of values.\n * @example\n *\n * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);\n * // => [1.2, 4.3]\n *\n * // using the `_.property` iteratee shorthand\n * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 2 }]\n */\n xorBy(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.xorWith DUMMY\n interface LoDashStatic {\n /**\n * This method is like `_.xor` except that it accepts `comparator` which is\n * invoked to compare elements of `arrays`. The comparator is invoked with\n * two arguments: (arrVal, othVal).\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of values.\n * @example\n *\n * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];\n * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];\n *\n * _.xorWith(objects, others, _.isEqual);\n * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]\n */\n xorWith(\n array: List,\n ...values: any[]\n ): any[];\n }\n\n //_.zip\n interface LoDashStatic {\n /**\n * Creates an array of grouped elements, the first of which contains the first elements of the given arrays,\n * the second of which contains the second elements of the given arrays, and so on.\n *\n * @param arrays The arrays to process.\n * @return Returns the new array of grouped elements.\n */\n zip(...arrays: Array | null | undefined>): T[][];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.zip\n */\n zip(...arrays: Array | null | undefined>): _.LoDashExplicitArrayWrapper;\n }\n\n //_.zipObject\n interface LoDashStatic {\n /**\n * This method is like _.fromPairs except that it accepts two arrays, one of property\n * identifiers and one of corresponding values.\n *\n * @param props The property names.\n * @param values The property values.\n * @return Returns the new object.\n */\n zipObject(\n props: List|List>,\n values?: List\n ): TResult;\n /**\n * This method is like _.zipObject except that it supports property paths.\n *\n * @param props The property names.\n * @param values The property values.\n * @return Returns the new object.\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): TResult;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n props: List|List>,\n values?: List\n ): TResult;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): TResult;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n props: List|List>,\n values?: List\n ): _.Dictionary;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n props: List|List>,\n values?: List\n ): _.Dictionary;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashImplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper;\n\n /**\n * @see _.zipObject\n */\n zipObject(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n /**\n * @see _.zipObjectDeep\n */\n zipObjectDeep(\n values?: List\n ): _.LoDashExplicitObjectWrapper<_.Dictionary>;\n }\n\n //_.zipWith\n interface LoDashStatic {\n /**\n * This method is like _.zip except that it accepts an iteratee to specify how grouped values should be\n * combined. The iteratee is bound to thisArg and invoked with four arguments: (accumulator, value, index,\n * group).\n * @param {...Array} [arrays] The arrays to process.\n * @param {Function} [iteratee] The function to combine grouped values.\n * @param {*} [thisArg] The `this` binding of `iteratee`.\n * @return Returns the new array of grouped elements.\n */\n zipWith(...args: any[]): TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.zipWith\n */\n zipWith(...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n /*********\n * Chain *\n *********/\n\n //_.chain\n interface LoDashStatic {\n /**\n * Creates a lodash object that wraps value with explicit method chaining enabled.\n *\n * @param value The value to wrap.\n * @return Returns the new lodash wrapper instance.\n */\n chain(value: number): LoDashExplicitWrapper;\n chain(value: string): LoDashExplicitWrapper;\n chain(value: boolean): LoDashExplicitWrapper;\n chain(value: null | undefined): LoDashExplicitWrapper;\n chain(value: T[]): LoDashExplicitArrayWrapper;\n chain(value: ReadonlyArray): LoDashExplicitArrayWrapper;\n chain(value: T[] | null | undefined): LoDashExplicitNillableArrayWrapper;\n chain(value: ReadonlyArray | null | undefined): LoDashExplicitNillableArrayWrapper;\n chain(value: T): LoDashExplicitObjectWrapper;\n chain(value: T | null | undefined): LoDashExplicitObjectWrapper;\n chain(value: any): LoDashExplicitWrapper;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashImplicitNillableArrayWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitNillableArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashImplicitNillableObjectWrapper {\n /**\n * @see _.chain\n */\n chain(): LoDashExplicitNillableObjectWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.chain\n */\n chain(): TWrapper;\n }\n\n //_.tap\n interface LoDashStatic {\n /**\n * This method invokes interceptor and returns value. The interceptor is bound to thisArg and invoked with one\n * argument; (value). The purpose of this method is to \"tap into\" a method chain in order to perform operations\n * on intermediate results within the chain.\n *\n * @param value The value to provide to interceptor.\n * @param interceptor The function to invoke.\n * @parem thisArg The this binding of interceptor.\n * @return Returns value.\n **/\n tap(\n value: T,\n interceptor: (value: T) => void\n ): T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.tap\n */\n tap(\n interceptor: (value: T) => void\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.tap\n */\n tap(\n interceptor: (value: T) => void\n ): TWrapper;\n }\n\n //_.thru\n interface LoDashStatic {\n /**\n * This method is like _.tap except that it returns the result of interceptor.\n *\n * @param value The value to provide to interceptor.\n * @param interceptor The function to invoke.\n * @param thisArg The this binding of interceptor.\n * @return Returns the result of interceptor.\n */\n thru(\n value: T,\n interceptor: (value: T) => TResult\n ): TResult;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.thru\n */\n thru(\n interceptor: (value: T) => TResult[]\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.commit\n interface LoDashImplicitWrapperBase {\n /**\n * Executes the chained sequence and returns the wrapped result.\n *\n * @return Returns the new lodash wrapper instance.\n */\n commit(): TWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.commit\n */\n commit(): TWrapper;\n }\n\n //_.prototype.concat\n interface LoDashImplicitWrapperBase {\n /**\n * Creates a new array joining a wrapped array with any additional arrays and/or values.\n *\n * @param items\n * @return Returns the new concatenated array.\n */\n concat(...items: Array>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.concat\n */\n concat(...items: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.plant\n interface LoDashImplicitWrapperBase {\n /**\n * Creates a clone of the chained sequence planting value as the wrapped value.\n * @param value The value to plant as the wrapped value.\n * @return Returns the new lodash wrapper instance.\n */\n plant(value: number): LoDashImplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: string): LoDashImplicitStringWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: boolean): LoDashImplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: number[]): LoDashImplicitNumberArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: ReadonlyArray): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: any): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.plant\n */\n plant(value: number): LoDashExplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: string): LoDashExplicitStringWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: boolean): LoDashExplicitWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: number[]): LoDashExplicitNumberArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: ReadonlyArray): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: T): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.plant\n */\n plant(value: any): LoDashExplicitWrapper;\n }\n\n //_.prototype.reverse\n interface LoDashImplicitArrayWrapper {\n /**\n * Reverses the wrapped array so the first element becomes the last, the second element becomes the second to\n * last, and so on.\n *\n * Note: This method mutates the wrapped array.\n *\n * @return Returns the new reversed lodash wrapper instance.\n */\n reverse(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.reverse\n */\n reverse(): LoDashExplicitArrayWrapper;\n }\n\n //_.prototype.toJSON\n interface LoDashWrapperBase {\n /**\n * @see _.value\n */\n toJSON(): T;\n }\n\n //_.prototype.toString\n interface LoDashWrapperBase {\n /**\n * Produces the result of coercing the unwrapped value to a string.\n *\n * @return Returns the coerced string value.\n */\n toString(): string;\n }\n\n //_.prototype.value\n interface LoDashWrapperBase {\n /**\n * Executes the chained sequence to extract the unwrapped value.\n *\n * @alias _.toJSON, _.valueOf\n *\n * @return Returns the resolved unwrapped value.\n */\n value(): T;\n }\n\n //_.valueOf\n interface LoDashWrapperBase {\n /**\n * @see _.value\n */\n valueOf(): T;\n }\n\n /**************\n * Collection *\n **************/\n\n //_.at\n interface LoDashStatic {\n /**\n * Creates an array of elements corresponding to the given keys, or indexes, of collection. Keys may be\n * specified as individual arguments or as arrays of keys.\n *\n * @param collection The collection to iterate over.\n * @param props The property names or indexes of elements to pick, specified individually or in arrays.\n * @return Returns the new array of picked elements.\n */\n at(\n collection: List|Dictionary | null | undefined,\n ...props: Array>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.at\n */\n at(...props: Array>): LoDashExplicitArrayWrapper;\n }\n\n //_.countBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is the number of times the key was returned by iteratee. The\n * iteratee is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n countBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: W\n ): Dictionary;\n\n /**\n * @see _.countBy\n */\n countBy(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.countBy\n */\n countBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.each\n interface LoDashStatic {\n each: typeof _.forEach;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEach\n */\n each(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEach\n */\n each(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n each(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.eachRight\n interface LoDashStatic {\n eachRight: typeof _.forEachRight;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n eachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.every\n interface LoDashStatic {\n /**\n * Checks if predicate returns truthy for all elements of collection. Iteration is stopped once predicate\n * returns falsey. The predicate is invoked with three arguments: (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @return Returns true if all elements pass the predicate check, else false.\n */\n every(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: NumericDictionary | null | undefined,\n predicate?: NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: string|any[]|PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): boolean;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.every\n */\n every(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: string|any[]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.every\n */\n every(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n //_.filter\n interface LoDashStatic {\n /**\n * Iterates over elements of collection, returning an array of all elements predicate returns truthy for. The\n * predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new filtered array.\n */\n filter(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard\n ): S[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard\n ): S[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: string | null | undefined,\n predicate?: StringIterator\n ): string[];\n\n /**\n * @see _.filter\n */\n filter(\n collection: List|Dictionary | null | undefined,\n predicate: string | [string, any] | RegExp | PartialObject\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.filter\n */\n filter(\n predicate?: StringIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | DictionaryIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.filter\n */\n filter(\n predicate?: StringIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIteratorTypeGuard\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.filter\n */\n filter(\n predicate: ListIterator | DictionaryIterator | string | [string, any] | RegExp | PartialObject\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.find\n interface LoDashStatic {\n /**\n * Iterates over elements of collection, returning the first element predicate returns truthy for.\n * The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to search.\n * @param predicate The function invoked per iteration.\n * @param fromIndex The index to search from.\n * @return Returns the matched element, else undefined.\n */\n find(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n \n /**\n * @see _.find\n */\n find(\n collection: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.find\n */\n find(\n collection: List|Dictionary | null | undefined,\n predicate?: string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | DictionaryIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): TResult|undefined;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.find\n */\n find(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): any;\n }\n\n //_.findLast\n interface LoDashStatic {\n /**\n * This method is like _.find except that it iterates over elements of a collection from\n * right to left.\n * @param collection Searches for a value in this list.\n * @param predicate The function called per iteration.\n * @param fromIndex The index to search from.\n * @return The found element, else undefined.\n **/\n findLast(\n collection: List | null | undefined,\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n \n /**\n * @see _.findLast\n */\n findLast(\n collection: List | null | undefined,\n predicate?: ListIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: Dictionary | null | undefined,\n predicate: DictionaryIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator,\n fromIndex?: number\n ): T|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n collection: List|Dictionary | null | undefined,\n predicate?: string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate: ListIteratorTypeGuard,\n fromIndex?: number\n ): S|undefined;\n\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): T|undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | DictionaryIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): TResult|undefined;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.findLast\n */\n findLast(\n predicate?: ListIterator | string | PartialObject | [string, any],\n fromIndex?: number\n ): any;\n }\n\n //_.flatMap\n interface LoDashStatic {\n /**\n * Creates an array of flattened values by running each element in collection through iteratee\n * and concating its result to the other mapped values. The iteratee is invoked with three arguments:\n * (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @return Returns the new flattened array.\n */\n flatMap(\n collection: List> | Dictionary> | NumericDictionary> | null | undefined\n ): T[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: List | null | undefined,\n iteratee: ListIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: Dictionary | null | undefined,\n iteratee: DictionaryIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: NumericDictionary | null | undefined,\n iteratee: NumericDictionaryIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: object | null | undefined,\n iteratee?: ObjectIterator> | string\n ): TResult[];\n\n /**\n * @see _.flatMap\n */\n flatMap(\n collection: object | null | undefined,\n iteratee: object\n ): boolean[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: StringIterator>\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|DictionaryIterator>|NumericDictionaryIterator>\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ObjectIterator>|string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: StringIterator>\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ListIterator>|DictionaryIterator>|NumericDictionaryIterator>\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: ObjectIterator>|string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(\n iteratee: object\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.flatMap\n */\n flatMap(): LoDashExplicitArrayWrapper;\n }\n\n //_.forEach\n interface LoDashStatic {\n /**\n * Iterates over elements of collection invoking iteratee for each element. The iteratee is bound to thisArg\n * and invoked with three arguments:\n * (value, index|key, collection). Iteratee functions may exit iteration early by explicitly returning false.\n *\n * Note: As with other \"Collections\" methods, objects with a \"length\" property are iterated like arrays. To\n * avoid this behavior _.forIn or _.forOwn may be used for object iteration.\n *\n * @alias _.each\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n */\n forEach(\n collection: TString,\n iteratee?: StringIterator\n ): TString;\n\n /**\n * @see _.forEach\n */\n forEach | null | undefined>(\n collection: TList & (List | null | undefined),\n iteratee?: ListIterator\n ): TList;\n\n /**\n * @see _.forEach\n */\n forEach | null | undefined>(\n collection: TDictionary & (Dictionary | null | undefined),\n iteratee?: DictionaryIterator\n ): TDictionary;\n\n /**\n * @see _.forEach\n */\n forEach(\n collection: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEach\n */\n forEach(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.forEachRight\n interface LoDashStatic {\n /**\n * This method is like _.forEach except that it iterates over elements of collection from right to left.\n *\n * @alias _.eachRight\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function called per iteration.\n * @param thisArg The this binding of callback.\n */\n forEachRight(\n collection: TString,\n iteratee?: StringIterator\n ): TString;\n\n /**\n * @see _.forEachRight\n */\n forEachRight | null | undefined>(\n collection: TList & (List | null | undefined),\n iteratee?: ListIterator\n ): TList;\n\n /**\n * @see _.forEachRight\n */\n forEachRight | null | undefined>(\n collection: TDictionary & (Dictionary | null | undefined),\n iteratee?: DictionaryIterator\n ): TDictionary;\n\n /**\n * @see _.forEachRight\n */\n forEachRight(\n collection: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: StringIterator\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: StringIterator\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee: ListIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forEachRight\n */\n forEachRight(\n iteratee?: ListIterator|DictionaryIterator\n ): TWrapper;\n }\n\n //_.groupBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is an array of the elements responsible for generating the\n * key. The iteratee is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n groupBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: TWhere\n ): Dictionary;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: Object\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: TWhere\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.groupBy\n */\n groupBy(\n iteratee?: Object\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.includes\n interface LoDashStatic {\n /**\n * Checks if target is in collection using SameValueZero for equality comparisons. If fromIndex is negative,\n * it’s used as the offset from the end of collection.\n *\n * @param collection The collection to search.\n * @param target The value to search for.\n * @param fromIndex The index to search from.\n * @return True if the target element is found, else false.\n */\n includes(\n collection: List|Dictionary | null | undefined,\n target: T,\n fromIndex?: number\n ): boolean;\n\n /**\n * @see _.includes\n */\n includes(\n collection: string | null | undefined,\n target: string,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: T,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: TValue,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.includes\n */\n includes(\n target: string,\n fromIndex?: number\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: T,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.includes\n */\n includes(\n target: TValue,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.includes\n */\n includes(\n target: string,\n fromIndex?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.keyBy\n interface LoDashStatic {\n /**\n * Creates an object composed of keys generated from the results of running each element of collection through\n * iteratee. The corresponding value of each key is the last element responsible for generating the key. The\n * iteratee function is bound to thisArg and invoked with three arguments:\n * (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the composed aggregate object.\n */\n keyBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: W\n ): Dictionary;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n collection: List|NumericDictionary|Dictionary | null | undefined,\n iteratee?: Object\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator|NumericDictionaryIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: Object\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: ListIterator|NumericDictionaryIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: W\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.keyBy\n */\n keyBy(\n iteratee?: Object\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.invoke\n interface LoDashStatic {\n /**\n * Invokes the method at path of object.\n * @param object The object to query.\n * @param path The path of the method to invoke.\n * @param args The arguments to invoke the method with.\n **/\n invoke(\n object: TObject,\n path: Many,\n ...args: any[]): TResult;\n\n /**\n * @see _.invoke\n **/\n invoke(\n object: Dictionary|TValue[],\n path: Many,\n ...args: any[]): TResult;\n\n /**\n * @see _.invoke\n **/\n invoke(\n object: any,\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invoke\n **/\n invoke(\n path: Many,\n ...args: any[]): TResult;\n }\n\n //_.invokeMap\n interface LoDashStatic {\n /**\n * Invokes the method named by methodName on each element in the collection returning\n * an array of the results of each invoked method. Additional arguments will be provided\n * to each invoked method. If methodName is a function it will be invoked for, and this\n * bound to, each element in the collection.\n * @param collection The collection to iterate over.\n * @param methodName The name of the method to invoke.\n * @param args Arguments to invoke the method with.\n **/\n invokeMap(\n collection: TValue[] | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Array<{}> | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary<{}> | null | undefined,\n methodName: string,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: TValue[] | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Array<{}> | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n collection: Dictionary<{}> | null | undefined,\n method: (...args: any[]) => TResult,\n ...args: any[]): TResult[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n methodName: string,\n ...args: any[]): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.invokeMap\n **/\n invokeMap(\n method: (...args: any[]) => TResult,\n ...args: any[]): LoDashExplicitArrayWrapper;\n }\n\n //_.map\n interface LoDashStatic {\n /**\n * Creates an array of values by running each element in collection through iteratee. The iteratee is bound to\n * thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for iteratee the created _.property style callback returns the property value\n * of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for iteratee the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * Many lodash methods are guarded to work as iteratees for methods like _.every, _.filter, _.map, _.mapValues,\n * _.reject, and _.some.\n *\n * The guarded methods are:\n * ary, callback, chunk, clone, create, curry, curryRight, drop, dropRight, every, fill, flatten, invert, max,\n * min, parseInt, slice, sortBy, take, takeRight, template, trim, trimLeft, trimRight, trunc, random, range,\n * sample, some, sum, uniq, and words\n *\n * @param collection The collection to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new mapped array.\n */\n map(\n collection: List | null | undefined,\n iteratee: ListIterator\n ): TResult[];\n\n /**\n * @see _.map\n */\n map(collection: List | null | undefined): T[];\n\n /**\n * @see _.map\n */\n map(\n collection: Dictionary | null | undefined,\n iteratee: DictionaryIterator\n ): TResult[];\n\n /** @see _.map */\n map(\n collection: Dictionary | null | undefined,\n iteratee: K\n ): T[K][];\n\n /** @see _.map */\n map(collection: Dictionary | null | undefined): T[];\n\n map(\n collection: NumericDictionary | null | undefined,\n iteratee?: NumericDictionaryIterator\n ): TResult[];\n\n /** @see _.map */\n map(collection: List | null | undefined, iteratee: K): T[K][];\n\n /**\n * @see _.map\n */\n map(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: string\n ): TResult[];\n\n /**\n * @see _.map\n */\n map(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n iteratee?: TObject\n ): boolean[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashImplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(iteratee: K): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.map\n */\n map(\n iteratee: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /** @see _.map */\n map(): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.map\n */\n map(\n iteratee: TObject\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.partition\n interface LoDashStatic {\n /**\n * Creates an array of elements split into two groups, the first of which contains elements predicate returns truthy for,\n * while the second of which contains elements predicate returns falsey for.\n * The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection).\n *\n * If a property name is provided for predicate the created _.property style callback\n * returns the property value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback\n * returns true for elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns\n * true for elements that have the properties of the given object, else false.\n *\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the array of grouped elements.\n **/\n partition(\n collection: List | null | undefined,\n callback: ListIterator): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n callback: DictionaryIterator): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n whereValue: W): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n whereValue: W): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n path: string,\n srcValue: any): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n path: string,\n srcValue: any): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: List | null | undefined,\n pluckValue: string): T[][];\n\n /**\n * @see _.partition\n **/\n partition(\n collection: Dictionary | null | undefined,\n pluckValue: string): T[][];\n }\n\n interface LoDashImplicitStringWrapper {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n whereValue: W): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n path: string,\n srcValue: any): LoDashImplicitArrayWrapper;\n /**\n * @see _.partition\n */\n partition(\n pluckValue: string): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.partition\n */\n partition(\n callback: ListIterator): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n callback: DictionaryIterator): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n path: string,\n srcValue: any): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.partition\n */\n partition(\n pluckValue: string): LoDashImplicitArrayWrapper;\n }\n\n //_.reduce\n interface LoDashStatic {\n /**\n * Reduces a collection to a value which is the accumulated result of running each\n * element in the collection through the callback, where each successive callback execution\n * consumes the return value of the previous execution. If accumulator is not provided the\n * first element of the collection will be used as the initial accumulator value. The callback\n * is bound to thisArg and invoked with four arguments; (accumulator, value, index|key, collection).\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param accumulator Initial value of the accumulator.\n * @param thisArg The this binding of callback.\n * @return Returns the accumulated value.\n **/\n reduce(\n collection: T[] | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: List | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: Dictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: NumericDictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: T[] | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: List | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: Dictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduce\n **/\n reduce(\n collection: NumericDictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): TResult | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator,\n accumulator: TResult): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.reduce\n **/\n reduce(\n callback: MemoIterator): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**LoDashExplicitWrapper\n * @see _.reduce\n */\n reduce(\n callback: MemoIterator,\n accumulator: TResult): LoDashExplicitWrapper;\n\n /**\n * @see _.reduce\n */\n reduce(\n callback: MemoIterator): LoDashExplicitWrapper;\n }\n\n //_.reduceRight\n interface LoDashStatic {\n /**\n * This method is like _.reduce except that it iterates over elements of a collection from\n * right to left.\n * @param collection The collection to iterate over.\n * @param callback The function called per iteration.\n * @param accumulator Initial value of the accumulator.\n * @param thisArg The this binding of callback.\n * @return The accumulated value.\n **/\n reduceRight(\n collection: T[] | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: List | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: Dictionary | null | undefined,\n callback: MemoIterator,\n accumulator: TResult): TResult;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: T[] | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: List | null | undefined,\n callback: MemoIterator): TResult | undefined;\n\n /**\n * @see _.reduceRight\n **/\n reduceRight(\n collection: Dictionary | null | undefined,\n callback: MemoIterator): TResult | undefined;\n }\n\n //_.reject\n interface LoDashStatic {\n /**\n * The opposite of _.filter; this method returns the elements of collection that predicate does not return\n * truthy for.\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the new filtered array.\n */\n reject(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: string | null | undefined,\n predicate?: StringIterator\n ): string[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: List|Dictionary | null | undefined,\n predicate: string\n ): T[];\n\n /**\n * @see _.reject\n */\n reject(\n collection: List|Dictionary | null | undefined,\n predicate: W\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.reject\n */\n reject(\n predicate?: StringIterator\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.reject\n */\n reject(\n predicate?: StringIterator\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.reject\n */\n reject(\n predicate: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(\n predicate: string\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.reject\n */\n reject(predicate: W): LoDashExplicitArrayWrapper;\n }\n\n //_.sample\n interface LoDashStatic {\n /**\n * Gets a random element from collection.\n *\n * @param collection The collection to sample.\n * @return Returns the random element.\n */\n sample(\n collection: List | Dictionary | NumericDictionary | object | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sample\n */\n sample(): string | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sample\n */\n sample(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sample\n */\n sample(): T | undefined;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sample\n */\n sample(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sample\n */\n sample(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sample\n */\n sample(): TWrapper;\n }\n\n //_.sampleSize\n interface LoDashStatic {\n /**\n * Gets n random elements at unique keys from collection up to the size of collection.\n *\n * @param collection The collection to sample.\n * @param n The number of elements to sample.\n * @return Returns the random elements.\n */\n sampleSize(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n n?: number\n ): T[];\n\n /**\n * @see _.sampleSize\n */\n sampleSize(\n collection: O | null | undefined,\n n?: number\n ): T[];\n\n /**\n * @see _.sampleSize\n */\n sampleSize(\n collection: Object | null | undefined,\n n?: number\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sampleSize\n */\n sampleSize(\n n?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.shuffle\n interface LoDashStatic {\n /**\n * Creates an array of shuffled values, using a version of the Fisher-Yates shuffle.\n *\n * @param collection The collection to shuffle.\n * @return Returns the new shuffled array.\n */\n shuffle(collection: List|Dictionary | null | undefined): T[];\n\n /**\n * @see _.shuffle\n */\n shuffle(collection: string | null | undefined): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.shuffle\n */\n shuffle(): LoDashExplicitArrayWrapper;\n }\n\n //_.size\n interface LoDashStatic {\n /**\n * Gets the size of collection by returning its length for array-like values or the number of own enumerable\n * properties for objects.\n *\n * @param collection The collection to inspect.\n * @return Returns the size of collection.\n */\n size(collection: List|Dictionary | null | undefined): number;\n\n /**\n * @see _.size\n */\n size(collection: string | null | undefined): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.size\n */\n size(): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.size\n */\n size(): LoDashExplicitWrapper;\n }\n\n //_.some\n interface LoDashStatic {\n /**\n * Checks if predicate returns truthy for any element of collection. Iteration is stopped once predicate\n * returns truthy. The predicate is invoked with three arguments: (value, index|key, collection).\n *\n * @param collection The collection to iterate over.\n * @param predicate The function invoked per iteration.\n * @return Returns true if any element passes the predicate check, else false.\n */\n some(\n collection: List | null | undefined,\n predicate?: ListIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Dictionary | null | undefined,\n predicate?: DictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: NumericDictionary | null | undefined,\n predicate?: NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: ObjectIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: PartialObject\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: List|Dictionary|NumericDictionary | null | undefined,\n predicate?: PartialObject\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n collection: Object | null | undefined,\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|NumericDictionaryIterator\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator|ObjectIterator\n ): boolean;\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): boolean;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): boolean;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|NumericDictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.some\n */\n some(\n predicate?: ListIterator|DictionaryIterator|NumericDictionaryIterator|ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: string|[string, any]\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.some\n */\n some(\n predicate?: PartialObject\n ): LoDashExplicitWrapper;\n }\n\n //_.sortBy\n interface LoDashStatic {\n /**\n * Creates an array of elements, sorted in ascending order by the results of\n * running each element in a collection through each iteratee. This method\n * performs a stable sort, that is, it preserves the original sort order of\n * equal elements. The iteratees are invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Collection\n * @param {Array|Object} collection The collection to iterate over.\n * @param {...(Function|Function[]|Object|Object[]|string|string[])} [iteratees=[_.identity]]\n * The iteratees to sort by, specified individually or in arrays.\n * @returns {Array} Returns the new sorted array.\n * @example\n *\n * var users = [\n * { 'user': 'fred', 'age': 48 },\n * { 'user': 'barney', 'age': 36 },\n * { 'user': 'fred', 'age': 42 },\n * { 'user': 'barney', 'age': 34 }\n * ];\n *\n * _.sortBy(users, function(o) { return o.user; });\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n *\n * _.sortBy(users, ['user', 'age']);\n * // => objects for [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]\n *\n * _.sortBy(users, 'user', function(o) {\n * return Math.floor(o.age / 10);\n * });\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n */\n sortBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined,\n iteratee: string\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined,\n whereValue: W\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List|Dictionary | null | undefined\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List | null | undefined,\n iteratees: Array|string|Object>\n ): T[];\n\n /**\n * @see _.sortBy\n */\n sortBy(\n collection: List | null | undefined,\n ...iteratees: Array|Object|string>\n ): T[];\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(...iteratees: Array|Object|string>): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n **/\n sortBy(iteratees: Array|string|Object>): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sortBy\n */\n sortBy(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(iteratee: string): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(whereValue: W): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.sortBy\n */\n sortBy(): LoDashExplicitArrayWrapper;\n }\n\n //_.orderBy\n interface LoDashStatic {\n /**\n * This method is like `_.sortBy` except that it allows specifying the sort\n * orders of the iteratees to sort by. If `orders` is unspecified, all values\n * are sorted in ascending order. Otherwise, specify an order of \"desc\" for\n * descending or \"asc\" for ascending sort order of corresponding values.\n *\n * @static\n * @memberOf _\n * @category Collection\n * @param {Array|Object} collection The collection to iterate over.\n * @param {Function[]|Object[]|string[]} [iteratees=[_.identity]] The iteratees to sort by.\n * @param {string[]} [orders] The sort orders of `iteratees`.\n * @param- {Object} [guard] Enables use as an iteratee for functions like `_.reduce`.\n * @returns {Array} Returns the new sorted array.\n * @example\n *\n * var users = [\n * { 'user': 'fred', 'age': 48 },\n * { 'user': 'barney', 'age': 34 },\n * { 'user': 'fred', 'age': 42 },\n * { 'user': 'barney', 'age': 36 }\n * ];\n *\n * // sort by `user` in ascending order and by `age` in descending order\n * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);\n * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]\n */\n orderBy(\n collection: List | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: List | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: NumericDictionary | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: NumericDictionary | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: Dictionary | null | undefined,\n iteratees?: Many|string|W>,\n orders?: Many\n ): T[];\n\n /**\n * @see _.orderBy\n */\n orderBy(\n collection: Dictionary | null | undefined,\n iteratees?: Many|string|Object>,\n orders?: Many\n ): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W|(ListIterator|string|W)>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|W>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.orderBy\n */\n orderBy(\n iteratees?: Many|string|Object>,\n orders?: Many\n ): LoDashExplicitArrayWrapper;\n }\n\n /********\n * Date *\n ********/\n\n //_.now\n interface LoDashStatic {\n /**\n * Gets the number of milliseconds that have elapsed since the Unix epoch (1 January 1970 00:00:00 UTC).\n *\n * @return The number of milliseconds.\n */\n now(): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.now\n */\n now(): number;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.now\n */\n now(): LoDashExplicitWrapper;\n }\n\n /*************\n * Functions *\n *************/\n\n //_.after\n interface LoDashStatic {\n /**\n * The opposite of _.before; this method creates a function that invokes func once it’s called n or more times.\n *\n * @param n The number of calls before func is invoked.\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n after(\n n: number,\n func: TFunc\n ): TFunc;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.after\n **/\n after(func: TFunc): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.after\n **/\n after(func: TFunc): LoDashExplicitObjectWrapper;\n }\n\n //_.ary\n interface LoDashStatic {\n /**\n * Creates a function that accepts up to n arguments ignoring any additional arguments.\n *\n * @param func The function to cap arguments for.\n * @param n The arity cap.\n * @returns Returns the new function.\n */\n ary(\n func: Function,\n n?: number\n ): TResult;\n\n ary(\n func: T,\n n?: number\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.ary\n */\n ary(n?: number): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.ary\n */\n ary(n?: number): LoDashExplicitObjectWrapper;\n }\n\n //_.before\n interface LoDashStatic {\n /**\n * Creates a function that invokes func, with the this binding and arguments of the created function, while\n * it’s called less than n times. Subsequent calls to the created function return the result of the last func\n * invocation.\n *\n * @param n The number of calls at which func is no longer invoked.\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n before(\n n: number,\n func: TFunc\n ): TFunc;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.before\n **/\n before(func: TFunc): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.before\n **/\n before(func: TFunc): LoDashExplicitObjectWrapper;\n }\n\n //_.bind\n interface FunctionBind {\n placeholder: any;\n\n (\n func: T,\n thisArg: any,\n ...partials: any[]\n ): TResult;\n\n (\n func: Function,\n thisArg: any,\n ...partials: any[]\n ): TResult;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of thisArg and prepends any additional _.bind\n * arguments to those provided to the bound function.\n *\n * The _.bind.placeholder value, which defaults to _ in monolithic builds, may be used as a placeholder for\n * partially applied arguments.\n *\n * Note: Unlike native Function#bind this method does not set the \"length\" property of bound functions.\n *\n * @param func The function to bind.\n * @param thisArg The this binding of func.\n * @param partials The arguments to be partially applied.\n * @return Returns the new bound function.\n */\n bind: FunctionBind;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bind\n */\n bind(\n thisArg: any,\n ...partials: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bind\n */\n bind(\n thisArg: any,\n ...partials: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.bindAll\n interface LoDashStatic {\n /**\n * Binds methods of an object to the object itself, overwriting the existing method. Method names may be\n * specified as individual arguments or as arrays of method names. If no method names are provided all\n * enumerable function properties, own and inherited, of object are bound.\n *\n * Note: This method does not set the \"length\" property of bound functions.\n *\n * @param object The object to bind and assign the bound methods to.\n * @param methodNames The object method names to bind, specified as individual method names or arrays of\n * method names.\n * @return Returns object.\n */\n bindAll(\n object: T,\n ...methodNames: Array>\n ): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bindAll\n */\n bindAll(...methodNames: Array>): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bindAll\n */\n bindAll(...methodNames: Array>): LoDashExplicitObjectWrapper;\n }\n\n //_.bindKey\n interface FunctionBindKey {\n placeholder: any;\n\n (\n object: T,\n key: any,\n ...partials: any[]\n ): TResult;\n\n (\n object: Object,\n key: any,\n ...partials: any[]\n ): TResult;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes the method at object[key] and prepends any additional _.bindKey arguments\n * to those provided to the bound function.\n *\n * This method differs from _.bind by allowing bound functions to reference methods that may be redefined\n * or don’t yet exist. See Peter Michaux’s article for more details.\n *\n * The _.bindKey.placeholder value, which defaults to _ in monolithic builds, may be used as a placeholder\n * for partially applied arguments.\n *\n * @param object The object the method belongs to.\n * @param key The key of the method.\n * @param partials The arguments to be partially applied.\n * @return Returns the new bound function.\n */\n bindKey: FunctionBindKey;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.bindKey\n */\n bindKey(\n key: any,\n ...partials: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.bindKey\n */\n bindKey(\n key: any,\n ...partials: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.createCallback\n interface LoDashStatic {\n /**\n * Produces a callback bound to an optional thisArg. If func is a property name the created\n * callback will return the property value for a given element. If func is an object the created\n * callback will return true for elements that contain the equivalent object properties,\n * otherwise it will return false.\n * @param func The value to convert to a callback.\n * @param thisArg The this binding of the created callback.\n * @param argCount The number of arguments the callback accepts.\n * @return A callback function.\n **/\n createCallback(\n func: string,\n argCount?: number): () => any;\n\n /**\n * @see _.createCallback\n **/\n createCallback(\n func: Dictionary,\n argCount?: number): () => boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.createCallback\n **/\n createCallback(\n argCount?: number): LoDashImplicitObjectWrapper<() => any>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.createCallback\n **/\n createCallback(\n argCount?: number): LoDashImplicitObjectWrapper<() => any>;\n }\n\n //_.curry\n interface LoDashStatic {\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1) => R):\n CurriedFunction1;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2) => R):\n CurriedFunction2;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3) => R):\n CurriedFunction3;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3, t4: T4) => R):\n CurriedFunction4;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curry(func: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R):\n CurriedFunction5;\n /**\n * Creates a function that accepts one or more arguments of func that when called either invokes func returning\n * its result, if all func arguments have been provided, or returns a function that accepts one or more of the\n * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient.\n * @param func The function to curry.\n * @param arity The arity of func.\n * @return Returns the new curried function.\n */\n curry(\n func: Function,\n arity?: number): TResult;\n }\n\n interface CurriedFunction1 {\n (): CurriedFunction1;\n (t1: T1): R;\n }\n\n interface CurriedFunction2 {\n (): CurriedFunction2;\n (t1: T1): CurriedFunction1;\n (t1: T1, t2: T2): R;\n }\n\n interface CurriedFunction3 {\n (): CurriedFunction3;\n (t1: T1): CurriedFunction2;\n (t1: T1, t2: T2): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3): R;\n }\n\n interface CurriedFunction4 {\n (): CurriedFunction4;\n (t1: T1): CurriedFunction3;\n (t1: T1, t2: T2): CurriedFunction2;\n (t1: T1, t2: T2, t3: T3): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3, t4: T4): R;\n }\n\n interface CurriedFunction5 {\n (): CurriedFunction5;\n (t1: T1): CurriedFunction4;\n (t1: T1, t2: T2): CurriedFunction3;\n (t1: T1, t2: T2, t3: T3): CurriedFunction2;\n (t1: T1, t2: T2, t3: T3, t4: T4): CurriedFunction1;\n (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5): R;\n }\n interface RightCurriedFunction1{\n ():RightCurriedFunction1\n (t1:T1):R\n }\n interface RightCurriedFunction2{\n ():RightCurriedFunction2\n (t2:T2):RightCurriedFunction1\n (t1:T1,t2:T2):R\n }\n interface RightCurriedFunction3{\n ():RightCurriedFunction3\n (t3:T3):RightCurriedFunction2\n (t2:T2,t3:T3):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3):R\n }\n interface RightCurriedFunction4{\n ():RightCurriedFunction4\n (t4:T4):RightCurriedFunction3\n (t3:T3,t4:T4):RightCurriedFunction2\n (t2:T2,t3:T3,t4:T4):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3,t4:T4):R\n }\n interface RightCurriedFunction5{\n ():RightCurriedFunction5\n (t5:T5):RightCurriedFunction4\n (t4:T4,t5:T5):RightCurriedFunction3\n (t3:T3,t4:T4,t5:T5):RightCurriedFunction2\n (t2:T2,t3:T3,t4:T4,t5:T5):RightCurriedFunction1\n (t1:T1,t2:T2,t3:T3,t4:T4,t5:T5):R\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.curry\n **/\n curry(arity?: number): LoDashImplicitObjectWrapper;\n }\n\n //_.curryRight\n interface LoDashStatic {\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1) => R):\n RightCurriedFunction1;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2) => R):\n RightCurriedFunction2;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3) => R):\n RightCurriedFunction3;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3, t4: T4) => R):\n RightCurriedFunction4;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @return Returns the new curried function.\n */\n curryRight(func: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) => R):\n RightCurriedFunction5;\n /**\n * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight\n * instead of _.partial.\n * @param func The function to curry.\n * @param arity The arity of func.\n * @return Returns the new curried function.\n */\n curryRight(\n func: Function,\n arity?: number): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.curryRight\n **/\n curryRight(arity?: number): LoDashImplicitObjectWrapper;\n }\n\n //_.debounce\n interface DebounceSettings {\n /**\n * Specify invoking on the leading edge of the timeout.\n */\n leading?: boolean;\n\n /**\n * The maximum time func is allowed to be delayed before it’s invoked.\n */\n maxWait?: number;\n\n /**\n * Specify invoking on the trailing edge of the timeout.\n */\n trailing?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since\n * the last time the debounced function was invoked. The debounced function comes with a cancel method to\n * cancel delayed invocations and a flush method to immediately invoke them. Provide an options object to\n * indicate that func should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent\n * calls to the debounced function return the result of the last func invocation.\n *\n * Note: If leading and trailing options are true, func is invoked on the trailing edge of the timeout only\n * if the the debounced function is invoked more than once during the wait timeout.\n *\n * See David Corbacho’s article for details over the differences between _.debounce and _.throttle.\n *\n * @param func The function to debounce.\n * @param wait The number of milliseconds to delay.\n * @param options The options object.\n * @param options.leading Specify invoking on the leading edge of the timeout.\n * @param options.maxWait The maximum time func is allowed to be delayed before it’s invoked.\n * @param options.trailing Specify invoking on the trailing edge of the timeout.\n * @return Returns the new debounced function.\n */\n debounce(\n func: T,\n wait?: number,\n options?: DebounceSettings\n ): T & Cancelable;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.debounce\n */\n debounce(\n wait?: number,\n options?: DebounceSettings\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.debounce\n */\n debounce(\n wait?: number,\n options?: DebounceSettings\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.defer\n interface LoDashStatic {\n /**\n * Defers invoking the func until the current call stack has cleared. Any additional arguments are provided to\n * func when it’s invoked.\n *\n * @param func The function to defer.\n * @param args The arguments to invoke the function with.\n * @return Returns the timer id.\n */\n defer(\n func: T,\n ...args: any[]\n ): number;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defer\n */\n defer(...args: any[]): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.defer\n */\n defer(...args: any[]): LoDashExplicitWrapper;\n }\n\n //_.delay\n interface LoDashStatic {\n /**\n * Invokes func after wait milliseconds. Any additional arguments are provided to func when it’s invoked.\n *\n * @param func The function to delay.\n * @param wait The number of milliseconds to delay invocation.\n * @param args The arguments to invoke the function with.\n * @return Returns the timer id.\n */\n delay(\n func: T,\n wait: number,\n ...args: any[]\n ): number;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.delay\n */\n delay(\n wait: number,\n ...args: any[]\n ): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.delay\n */\n delay(\n wait: number,\n ...args: any[]\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that invokes `func` with arguments reversed.\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to flip arguments for.\n * @returns {Function} Returns the new function.\n * @example\n *\n * var flipped = _.flip(function() {\n * return _.toArray(arguments);\n * });\n *\n * flipped('a', 'b', 'c', 'd');\n * // => ['d', 'c', 'b', 'a']\n */\n flip(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flip\n */\n flip(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flip\n */\n flip(): LoDashExplicitObjectWrapper;\n }\n\n //_.flow\n interface LoDashStatic {\n /**\n * Creates a function that returns the result of invoking the provided functions with the this binding of the\n * created function, where each successive invocation is supplied the return value of the previous.\n *\n * @param funcs Functions to invoke.\n * @return Returns the new function.\n */\n // 0-argument first function\n flow(f1: () => R1, f2: (a: R1) => R2): () => R2;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): () => R3;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): () => R4;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): () => R5;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): () => R6;\n flow(f1: () => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): () => R7;\n // 1-argument first function\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2): (a1: A1) => R2;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1) => R3;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1) => R4;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1) => R5;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1) => R6;\n flow(f1: (a1: A1) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1) => R7;\n // 2-argument first function\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2) => R2;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2) => R3;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2) => R4;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2) => R5;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2) => R6;\n flow(f1: (a1: A1, a2: A2) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2) => R7;\n // 3-argument first function\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2, a3: A3) => R2;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2, a3: A3) => R3;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2, a3: A3) => R4;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2, a3: A3) => R5;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2, a3: A3) => R6;\n flow(f1: (a1: A1, a2: A2, a3: A3) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2, a3: A3) => R7;\n // 4-argument first function\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2): (a1: A1, a2: A2, a3: A3, a4: A4) => R2;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3): (a1: A1, a2: A2, a3: A3, a4: A4) => R3;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4): (a1: A1, a2: A2, a3: A3, a4: A4) => R4;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5): (a1: A1, a2: A2, a3: A3, a4: A4) => R5;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6): (a1: A1, a2: A2, a3: A3, a4: A4) => R6;\n flow(f1: (a1: A1, a2: A2, a3: A3, a4: A4) => R1, f2: (a: R1) => R2, f3: (a: R2) => R3, f4: (a: R3) => R4, f5: (a: R4) => R5, f6: (a: R5) => R6, f7: (a: R6) => R7): (a1: A1, a2: A2, a3: A3, a4: A4) => R7;\n // generic function\n flow(...funcs: Function[]): TResult;\n flow(funcs: Function[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flow\n */\n flow(...funcs: Function[]): LoDashImplicitObjectWrapper;\n /**\n * @see _.flow\n */\n flow(funcs: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flow\n */\n flow(...funcs: Function[]): LoDashExplicitObjectWrapper;\n /**\n * @see _.flow\n */\n flow(funcs: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.flowRight\n interface LoDashStatic {\n /**\n * This method is like _.flow except that it creates a function that invokes the provided functions from right\n * to left.\n *\n * @param funcs Functions to invoke.\n * @return Returns the new function.\n */\n flowRight(...funcs: Function[]): TResult;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.flowRight\n */\n flowRight(...funcs: Function[]): LoDashImplicitObjectWrapper;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.flowRight\n */\n flowRight(...funcs: Function[]): LoDashExplicitObjectWrapper;\n /**\n * @see _.flowRight\n */\n flowRight(funcs: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.memoize\n interface MemoizedFunction extends Function {\n cache: MapCache;\n }\n\n interface LoDashStatic {\n /**\n * Creates a function that memoizes the result of func. If resolver is provided it determines the cache key for\n * storing the result based on the arguments provided to the memoized function. By default, the first argument\n * provided to the memoized function is coerced to a string and used as the cache key. The func is invoked with\n * the this binding of the memoized function.\n *\n * @param func The function to have its output memoized.\n * @param resolver The function to resolve the cache key.\n * @return Returns the new memoizing function.\n */\n memoize: {\n (func: T, resolver?: Function): T & MemoizedFunction;\n Cache: MapCacheConstructor;\n };\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.memoize\n */\n memoize(resolver?: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.memoize\n */\n memoize(resolver?: Function): LoDashExplicitObjectWrapper;\n }\n\n //_.overArgs (was _.modArgs)\n interface LoDashStatic {\n /**\n * Creates a function that runs each argument through a corresponding transform function.\n *\n * @param func The function to wrap.\n * @param transforms The functions to transform arguments, specified as individual functions or arrays\n * of functions.\n * @return Returns the new function.\n */\n overArgs(\n func: T,\n ...transforms: Function[]\n ): TResult;\n\n /**\n * @see _.overArgs\n */\n overArgs(\n func: T,\n transforms: Function[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overArgs\n */\n overArgs(...transforms: Function[]): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.overArgs\n */\n overArgs(transforms: Function[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overArgs\n */\n overArgs(...transforms: Function[]): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.overArgs\n */\n overArgs(transforms: Function[]): LoDashExplicitObjectWrapper;\n }\n\n //_.negate\n interface LoDashStatic {\n /**\n * Creates a function that negates the result of the predicate func. The func predicate is invoked with\n * the this binding and arguments of the created function.\n *\n * @param predicate The predicate to negate.\n * @return Returns the new function.\n */\n negate(predicate: T): (...args: any[]) => boolean;\n\n /**\n * @see _.negate\n */\n negate(predicate: T): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.negate\n */\n negate(): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n\n /**\n * @see _.negate\n */\n negate(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.negate\n */\n negate(): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n\n /**\n * @see _.negate\n */\n negate(): LoDashExplicitObjectWrapper;\n }\n\n //_.once\n interface LoDashStatic {\n /**\n * Creates a function that is restricted to invoking func once. Repeat calls to the function return the value\n * of the first call. The func is invoked with the this binding and arguments of the created function.\n *\n * @param func The function to restrict.\n * @return Returns the new restricted function.\n */\n once(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.once\n */\n once(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.once\n */\n once(): LoDashExplicitObjectWrapper;\n }\n\n //_.partial\n interface LoDashStatic {\n /**\n * Creates a function that, when called, invokes func with any additional partial arguments\n * prepended to those provided to the new function. This method is similar to _.bind except\n * it does not alter the this binding.\n * @param func The function to partially apply arguments to.\n * @param args Arguments to be partially applied.\n * @return The new partially applied function.\n **/\n partial: Partial;\n }\n\n type PH = LoDashStatic;\n\n type Function0 = () => R;\n type Function1 = (t1: T1) => R;\n type Function2 = (t1: T1, t2: T2) => R;\n type Function3 = (t1: T1, t2: T2, t3: T3) => R;\n type Function4 = (t1: T1, t2: T2, t3: T3, t4: T4) => R;\n\n interface Partial {\n // arity 0\n (func: Function0): Function0;\n // arity 1\n (func: Function1): Function1;\n (func: Function1, arg1: T1): Function0;\n // arity 2\n (func: Function2): Function2;\n (func: Function2, arg1: T1): Function1< T2, R>;\n (func: Function2, plc1: PH, arg2: T2): Function1;\n (func: Function2, arg1: T1, arg2: T2): Function0< R>;\n // arity 3\n (func: Function3): Function3;\n (func: Function3, arg1: T1): Function2< T2, T3, R>;\n (func: Function3, plc1: PH, arg2: T2): Function2;\n (func: Function3, arg1: T1, arg2: T2): Function1< T3, R>;\n (func: Function3, plc1: PH, plc2: PH, arg3: T3): Function2;\n (func: Function3, arg1: T1, plc2: PH, arg3: T3): Function1< T2, R>;\n (func: Function3, plc1: PH, arg2: T2, arg3: T3): Function1;\n (func: Function3, arg1: T1, arg2: T2, arg3: T3): Function0< R>;\n // arity 4\n (func: Function4): Function4;\n (func: Function4, arg1: T1): Function3< T2, T3, T4, R>;\n (func: Function4, plc1: PH, arg2: T2): Function3;\n (func: Function4, arg1: T1, arg2: T2): Function2< T3, T4, R>;\n (func: Function4, plc1: PH, plc2: PH, arg3: T3): Function3;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3): Function2< T2, T4, R>;\n (func: Function4, plc1: PH, arg2: T2, arg3: T3): Function2;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3): Function1< T4, R>;\n (func: Function4, plc1: PH, plc2: PH, plc3: PH, arg4: T4): Function3;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, arg4: T4): Function2< T2, T3, R>;\n (func: Function4, plc1: PH, arg2: T2, plc3: PH, arg4: T4): Function2;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, arg4: T4): Function1< T3, R>;\n (func: Function4, plc1: PH, plc2: PH, arg3: T3, arg4: T4): Function2;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, arg4: T4): Function1< T2, R>;\n (func: Function4, plc1: PH, arg2: T2, arg3: T3, arg4: T4): Function1;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, arg4: T4): Function0< R>;\n // catch-all\n (func: Function, ...args: any[]): Function;\n }\n\n //_.partialRight\n interface LoDashStatic {\n /**\n * This method is like _.partial except that partial arguments are appended to those provided\n * to the new function.\n * @param func The function to partially apply arguments to.\n * @param args Arguments to be partially applied.\n * @return The new partially applied function.\n **/\n partialRight: PartialRight;\n }\n\n interface PartialRight {\n // arity 0\n (func: Function0): Function0;\n // arity 1\n (func: Function1): Function1;\n (func: Function1, arg1: T1): Function0;\n // arity 2\n (func: Function2): Function2;\n (func: Function2, arg1: T1, plc2: PH): Function1< T2, R>;\n (func: Function2, arg2: T2): Function1;\n (func: Function2, arg1: T1, arg2: T2): Function0< R>;\n // arity 3\n (func: Function3): Function3;\n (func: Function3, arg1: T1, plc2: PH, plc3: PH): Function2< T2, T3, R>;\n (func: Function3, arg2: T2, plc3: PH): Function2;\n (func: Function3, arg1: T1, arg2: T2, plc3: PH): Function1< T3, R>;\n (func: Function3, arg3: T3): Function2;\n (func: Function3, arg1: T1, plc2: PH, arg3: T3): Function1< T2, R>;\n (func: Function3, arg2: T2, arg3: T3): Function1;\n (func: Function3, arg1: T1, arg2: T2, arg3: T3): Function0< R>;\n // arity 4\n (func: Function4): Function4;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, plc4: PH): Function3< T2, T3, T4, R>;\n (func: Function4, arg2: T2, plc3: PH, plc4: PH): Function3;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, plc4: PH): Function2< T3, T4, R>;\n (func: Function4, arg3: T3, plc4: PH): Function3;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, plc4: PH): Function2< T2, T4, R>;\n (func: Function4, arg2: T2, arg3: T3, plc4: PH): Function2;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, plc4: PH): Function1< T4, R>;\n (func: Function4, arg4: T4): Function3;\n (func: Function4, arg1: T1, plc2: PH, plc3: PH, arg4: T4): Function2< T2, T3, R>;\n (func: Function4, arg2: T2, plc3: PH, arg4: T4): Function2;\n (func: Function4, arg1: T1, arg2: T2, plc3: PH, arg4: T4): Function1< T3, R>;\n (func: Function4, arg3: T3, arg4: T4): Function2;\n (func: Function4, arg1: T1, plc2: PH, arg3: T3, arg4: T4): Function1< T2, R>;\n (func: Function4, arg2: T2, arg3: T3, arg4: T4): Function1;\n (func: Function4, arg1: T1, arg2: T2, arg3: T3, arg4: T4): Function0< R>;\n // catch-all\n (func: Function, ...args: any[]): Function;\n }\n\n //_.rearg\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with arguments arranged according to the specified indexes where the\n * argument value at the first index is provided as the first argument, the argument value at the second index\n * is provided as the second argument, and so on.\n * @param func The function to rearrange arguments for.\n * @param indexes The arranged argument indexes, specified as individual indexes or arrays of indexes.\n * @return Returns the new function.\n */\n rearg(func: Function, indexes: number[]): TResult;\n\n /**\n * @see _.rearg\n */\n rearg(func: Function, ...indexes: number[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.rearg\n */\n rearg(indexes: number[]): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.rearg\n */\n rearg(...indexes: number[]): LoDashImplicitObjectWrapper;\n }\n\n //_.rest\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of the created function and arguments from start\n * and beyond provided as an array.\n *\n * Note: This method is based on the rest parameter.\n *\n * @param func The function to apply a rest parameter to.\n * @param start The start position of the rest parameter.\n * @return Returns the new function.\n */\n rest(\n func: Function,\n start?: number\n ): TResult;\n\n /**\n * @see _.rest\n */\n rest(\n func: TFunc,\n start?: number\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.rest\n */\n rest(start?: number): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.rest\n */\n rest(start?: number): LoDashExplicitObjectWrapper;\n }\n\n //_.spread\n interface LoDashStatic {\n /**\n * Creates a function that invokes func with the this binding of the created function and an array of arguments\n * much like Function#apply.\n *\n * Note: This method is based on the spread operator.\n *\n * @param func The function to spread arguments over.\n * @return Returns the new function.\n */\n spread(func: F): T;\n\n /**\n * @see _.spread\n */\n spread(func: Function): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.spread\n */\n spread(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.spread\n */\n spread(): LoDashExplicitObjectWrapper;\n }\n\n //_.throttle\n interface ThrottleSettings {\n /**\n * If you'd like to disable the leading-edge call, pass this as false.\n */\n leading?: boolean;\n\n /**\n * If you'd like to disable the execution on the trailing-edge, pass false.\n */\n trailing?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Creates a throttled function that only invokes func at most once per every wait milliseconds. The throttled\n * function comes with a cancel method to cancel delayed invocations and a flush method to immediately invoke\n * them. Provide an options object to indicate that func should be invoked on the leading and/or trailing edge\n * of the wait timeout. Subsequent calls to the throttled function return the result of the last func call.\n *\n * Note: If leading and trailing options are true, func is invoked on the trailing edge of the timeout only if\n * the the throttled function is invoked more than once during the wait timeout.\n *\n * @param func The function to throttle.\n * @param wait The number of milliseconds to throttle invocations to.\n * @param options The options object.\n * @param options.leading Specify invoking on the leading edge of the timeout.\n * @param options.trailing Specify invoking on the trailing edge of the timeout.\n * @return Returns the new throttled function.\n */\n throttle(\n func: T,\n wait?: number,\n options?: ThrottleSettings\n ): T & Cancelable;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.throttle\n */\n throttle(\n wait?: number,\n options?: ThrottleSettings\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.throttle\n */\n throttle(\n wait?: number,\n options?: ThrottleSettings\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.unary\n interface LoDashStatic {\n /**\n * Creates a function that accepts up to one argument, ignoring any\n * additional arguments.\n *\n * @static\n * @memberOf _\n * @category Function\n * @param {Function} func The function to cap arguments for.\n * @returns {Function} Returns the new function.\n * @example\n *\n * _.map(['6', '8', '10'], _.unary(parseInt));\n * // => [6, 8, 10]\n */\n unary(func: T): T;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.unary\n */\n unary(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.unary\n */\n unary(): LoDashExplicitObjectWrapper;\n }\n\n //_.wrap\n interface LoDashStatic {\n /**\n * Creates a function that provides value to the wrapper function as its first argument. Any additional\n * arguments provided to the function are appended to those provided to the wrapper function. The wrapper is\n * invoked with the this binding of the created function.\n *\n * @param value The value to wrap.\n * @param wrapper The wrapper function.\n * @return Returns the new function.\n */\n wrap(\n value: V,\n wrapper: W\n ): R;\n\n /**\n * @see _.wrap\n */\n wrap(\n value: V,\n wrapper: Function\n ): R;\n\n /**\n * @see _.wrap\n */\n wrap(\n value: any,\n wrapper: Function\n ): R;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.wrap\n */\n wrap(wrapper: W): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.wrap\n */\n wrap(wrapper: Function): LoDashExplicitObjectWrapper;\n }\n\n /********\n * Lang *\n ********/\n\n //_.castArray\n interface LoDashStatic {\n /**\n * Casts value as an array if it’s not one.\n *\n * @param value The value to inspect.\n * @return Returns the cast array.\n */\n castArray(value?: Many): T[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.castArray\n */\n castArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): TWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.castArray\n */\n castArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.castArray\n */\n castArray(): LoDashExplicitArrayWrapper;\n }\n\n //_.clone\n interface LoDashStatic {\n /**\n * Creates a shallow clone of value.\n *\n * Note: This method is loosely based on the structured clone algorithm and supports cloning arrays,\n * array buffers, booleans, date objects, maps, numbers, Object objects, regexes, sets, strings, symbols,\n * and typed arrays. The own enumerable properties of arguments objects are cloned as plain objects. An empty\n * object is returned for uncloneable values such as error objects, functions, DOM nodes, and WeakMaps.\n *\n * @param value The value to clone.\n * @return Returns the cloned value.\n */\n clone(value: T): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.clone\n */\n clone(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.clone\n */\n clone(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.clone\n */\n clone(): TWrapper;\n }\n\n //_.cloneDeep\n interface LoDashStatic {\n /**\n * This method is like _.clone except that it recursively clones value.\n *\n * @param value The value to recursively clone.\n * @return Returns the deep cloned value.\n */\n cloneDeep(value: T): T;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneDeep\n */\n cloneDeep(): TWrapper;\n }\n\n //_.cloneDeepWith\n type CloneDeepWithCustomizer = (value: TValue, key?: number|string, object?: any, stack?: any) => TResult;\n\n interface LoDashStatic {\n /**\n * This method is like _.cloneWith except that it recursively clones value.\n *\n * @param value The value to recursively clone.\n * @param customizer The function to customize cloning.\n * @return Returns the deep cloned value.\n */\n cloneDeepWith(\n value: any,\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n\n /**\n * @see _.clonDeepeWith\n */\n cloneDeepWith(\n value: T,\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneDeepWith\n */\n cloneDeepWith(\n customizer?: CloneDeepWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.cloneWith\n type CloneWithCustomizer = (value: TValue, key?: number|string, object?: any, stack?: any) => TResult;\n\n interface LoDashStatic {\n /**\n * This method is like _.clone except that it accepts customizer which is invoked to produce the cloned value.\n * If customizer returns undefined cloning is handled by the method instead.\n *\n * @param value The value to clone.\n * @param customizer The function to customize cloning.\n * @return Returns the cloned value.\n */\n cloneWith(\n value: any,\n customizer?: CloneWithCustomizer\n ): TResult;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n value: T,\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.cloneWith\n */\n cloneWith(\n customizer?: CloneWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n /**\n * An object containing predicate functions for each property of T\n */\n type ConformsPredicateObject = {\n [P in keyof T]: (val: T[P]) => boolean;\n };\n\n //_.conforms\n interface LoDashStatic {\n /**\n * Creates a function that invokes the predicate properties of `source` with the corresponding\n * property values of a given object, returning true if all predicates return truthy, else false.\n */\n conforms(source: ConformsPredicateObject): (Target: T) => boolean;\n }\n\n //_.conformsTo\n interface LoDashStatic {\n /**\n * Checks if object conforms to source by invoking the predicate properties of source with the\n * corresponding property values of object.\n *\n * Note: This method is equivalent to _.conforms when source is partially applied.\n */\n conformsTo(object: T, source: ConformsPredicateObject): boolean;\n }\n\n //_.eq\n interface LoDashStatic {\n /**\n * Performs a [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'user': 'fred' };\n * var other = { 'user': 'fred' };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\n eq(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n eq(\n other: any\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n eq(\n other: any\n ): LoDashExplicitWrapper;\n }\n\n //_.gt\n interface LoDashStatic {\n /**\n * Checks if value is greater than other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is greater than other, else false.\n */\n gt(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.gt\n */\n gt(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.gt\n */\n gt(other: any): LoDashExplicitWrapper;\n }\n\n //_.gte\n interface LoDashStatic {\n /**\n * Checks if value is greater than or equal to other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is greater than or equal to other, else false.\n */\n gte(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.gte\n */\n gte(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.gte\n */\n gte(other: any): LoDashExplicitWrapper;\n }\n\n //_.isArguments\n interface LoDashStatic {\n /**\n * Checks if value is classified as an arguments object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isArguments(value?: any): value is IArguments;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArguments\n */\n isArguments(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArguments\n */\n isArguments(): LoDashExplicitWrapper;\n }\n\n //_.isArray\n interface LoDashStatic {\n /**\n * Checks if value is classified as an Array object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isArray(value?: any): value is any[];\n\n /**\n * DEPRECATED\n */\n isArray(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArray\n */\n isArray(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArray\n */\n isArray(): LoDashExplicitWrapper;\n }\n\n //_.isArrayBuffer\n interface LoDashStatic {\n /**\n * Checks if value is classified as an ArrayBuffer object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isArrayBuffer(value?: any): value is ArrayBuffer;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayBuffer\n */\n isArrayBuffer(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayBuffer\n */\n isArrayBuffer(): LoDashExplicitWrapper;\n }\n\n //_.isArrayLike\n interface LoDashStatic {\n /**\n * Checks if `value` is array-like. A value is considered array-like if it's\n * not a function and has a `value.length` that's an integer greater than or\n * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.\n *\n * @static\n * @memberOf _\n * @type Function\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n * @example\n *\n * _.isArrayLike([1, 2, 3]);\n * // => true\n *\n * _.isArrayLike(document.body.children);\n * // => true\n *\n * _.isArrayLike('abc');\n * // => true\n *\n * _.isArrayLike(_.noop);\n * // => false\n */\n isArrayLike(value: T & string & number): boolean; // should only match if T = any\n\n /**\n * @see _.isArrayLike\n */\n isArrayLike(value?: Function): value is never;\n\n /**\n * @see _.isArrayLike\n */\n isArrayLike(value: T | Function): value is T & { length: number };\n\n /**\n * DEPRECATED\n */\n isArrayLike(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayLike\n */\n isArrayLike(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayLike\n */\n isArrayLike(): LoDashExplicitWrapper;\n }\n\n //_.isArrayLikeObject\n interface LoDashStatic {\n /**\n * This method is like `_.isArrayLike` except that it also checks if `value`\n * is an object.\n *\n * @static\n * @memberOf _\n * @type Function\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.\n * @example\n *\n * _.isArrayLikeObject([1, 2, 3]);\n * // => true\n *\n * _.isArrayLikeObject(document.body.children);\n * // => true\n *\n * _.isArrayLikeObject('abc');\n * // => false\n *\n * _.isArrayLikeObject(_.noop);\n * // => false\n */\n isArrayLikeObject(value: T & string & number): boolean; // should only match if T = any\n\n /**\n * @see _.isArrayLike\n */\n isArrayLikeObject(value?: Function | string | boolean | number): value is never;\n\n /**\n * @see _.isArrayLike\n */\n isArrayLikeObject(value: T | Function | string | boolean | number): value is T & { length: number };\n\n /**\n * DEPRECATED\n */\n isArrayLikeObject(value?: any): value is any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isArrayLikeObject\n */\n isArrayLikeObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isArrayLikeObject\n */\n isArrayLikeObject(): LoDashExplicitWrapper;\n }\n\n //_.isBoolean\n interface LoDashStatic {\n /**\n * Checks if value is classified as a boolean primitive or object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isBoolean(value?: any): value is boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isBoolean\n */\n isBoolean(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isBoolean\n */\n isBoolean(): LoDashExplicitWrapper;\n }\n\n //_.isBuffer\n interface LoDashStatic {\n /**\n * Checks if value is a buffer.\n *\n * @param value The value to check.\n * @return Returns true if value is a buffer, else false.\n */\n isBuffer(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isBuffer\n */\n isBuffer(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isBuffer\n */\n isBuffer(): LoDashExplicitWrapper;\n }\n\n //_.isDate\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Date object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isDate(value?: any): value is Date;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isDate\n */\n isDate(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isDate\n */\n isDate(): LoDashExplicitWrapper;\n }\n\n //_.isElement\n interface LoDashStatic {\n /**\n * Checks if value is a DOM element.\n *\n * @param value The value to check.\n * @return Returns true if value is a DOM element, else false.\n */\n isElement(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isElement\n */\n isElement(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isElement\n */\n isElement(): LoDashExplicitWrapper;\n }\n\n //_.isEmpty\n interface LoDashStatic {\n /**\n * Checks if value is empty. A value is considered empty unless it’s an arguments object, array, string, or\n * jQuery-like collection with a length greater than 0 or an object with own enumerable properties.\n *\n * @param value The value to inspect.\n * @return Returns true if value is empty, else false.\n */\n isEmpty(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEmpty\n */\n isEmpty(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEmpty\n */\n isEmpty(): LoDashExplicitWrapper;\n }\n\n //_.isEqual\n interface LoDashStatic {\n /**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n *\n * **Note:** This method supports comparing arrays, array buffers, booleans,\n * date objects, error objects, maps, numbers, `Object` objects, regexes,\n * sets, strings, symbols, and typed arrays. `Object` objects are compared\n * by their own, not inherited, enumerable properties. Functions and DOM\n * nodes are **not** supported.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'user': 'fred' };\n * var other = { 'user': 'fred' };\n *\n * _.isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n */\n isEqual(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n isEqual(\n other: any\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqual\n */\n isEqual(\n other: any\n ): LoDashExplicitWrapper;\n }\n\n // _.isEqualWith\n type IsEqualCustomizer = (value: any, other: any, indexOrKey: number|string|undefined, parent: any, otherParent: any, stack: any) => boolean|undefined;\n\n interface LoDashStatic {\n /**\n * This method is like `_.isEqual` except that it accepts `customizer` which is\n * invoked to compare values. If `customizer` returns `undefined` comparisons are\n * handled by the method instead. The `customizer` is invoked with up to seven arguments:\n * (objValue, othValue [, index|key, object, other, stack]).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @param {Function} [customizer] The function to customize comparisons.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * function isGreeting(value) {\n * return /^h(?:i|ello)$/.test(value);\n * }\n *\n * function customizer(objValue, othValue) {\n * if (isGreeting(objValue) && isGreeting(othValue)) {\n * return true;\n * }\n * }\n *\n * var array = ['hello', 'goodbye'];\n * var other = ['hi', 'goodbye'];\n *\n * _.isEqualWith(array, other, customizer);\n * // => true\n */\n isEqualWith(\n value: any,\n other: any,\n customizer?: IsEqualCustomizer\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isEqualWith\n */\n isEqualWith(\n other: any,\n customizer?: IsEqualCustomizer\n ): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isEqualWith\n */\n isEqualWith(\n other: any,\n customizer?: IsEqualCustomizer\n ): LoDashExplicitWrapper;\n }\n\n //_.isError\n interface LoDashStatic {\n /**\n * Checks if value is an Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, or URIError\n * object.\n *\n * @param value The value to check.\n * @return Returns true if value is an error object, else false.\n */\n isError(value: any): value is Error;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isError\n */\n isError(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isError\n */\n isError(): LoDashExplicitWrapper;\n }\n\n //_.isFinite\n interface LoDashStatic {\n /**\n * Checks if value is a finite primitive number.\n *\n * Note: This method is based on Number.isFinite.\n *\n * @param value The value to check.\n * @return Returns true if value is a finite number, else false.\n */\n isFinite(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isFinite\n */\n isFinite(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isFinite\n */\n isFinite(): LoDashExplicitWrapper;\n }\n\n //_.isFunction\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Function object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isFunction(value?: any): value is Function;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isFunction\n */\n isFunction(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isFunction\n */\n isFunction(): LoDashExplicitWrapper;\n }\n\n //_.isInteger\n interface LoDashStatic {\n /**\n * Checks if `value` is an integer.\n *\n * **Note:** This method is based on [`Number.isInteger`](https://mdn.io/Number/isInteger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an integer, else `false`.\n * @example\n *\n * _.isInteger(3);\n * // => true\n *\n * _.isInteger(Number.MIN_VALUE);\n * // => false\n *\n * _.isInteger(Infinity);\n * // => false\n *\n * _.isInteger('3');\n * // => false\n */\n isInteger(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isInteger\n */\n isInteger(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isInteger\n */\n isInteger(): LoDashExplicitWrapper;\n }\n\n //_.isLength\n interface LoDashStatic {\n /**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n * @example\n *\n * _.isLength(3);\n * // => true\n *\n * _.isLength(Number.MIN_VALUE);\n * // => false\n *\n * _.isLength(Infinity);\n * // => false\n *\n * _.isLength('3');\n * // => false\n */\n isLength(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isLength\n */\n isLength(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isLength\n */\n isLength(): LoDashExplicitWrapper;\n }\n\n //_.isMap\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Map object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isMap(value?: any): value is Map;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isMap\n */\n isMap(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isMap\n */\n isMap(): LoDashExplicitWrapper;\n }\n\n //_.isMatch\n type isMatchCustomizer = (value: any, other: any, indexOrKey?: number|string) => boolean;\n\n interface LoDashStatic {\n /**\n * Performs a deep comparison between `object` and `source` to determine if\n * `object` contains equivalent property values.\n *\n * **Note:** This method supports comparing the same values as `_.isEqual`.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {Object} object The object to inspect.\n * @param {Object} source The object of property values to match.\n * @returns {boolean} Returns `true` if `object` is a match, else `false`.\n * @example\n *\n * var object = { 'user': 'fred', 'age': 40 };\n *\n * _.isMatch(object, { 'age': 40 });\n * // => true\n *\n * _.isMatch(object, { 'age': 36 });\n * // => false\n */\n isMatch(object: Object, source: Object): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.isMatch\n */\n isMatch(source: Object): boolean;\n }\n\n //_.isMatchWith\n type isMatchWithCustomizer = (value: any, other: any, indexOrKey?: number|string) => boolean;\n\n interface LoDashStatic {\n /**\n * This method is like `_.isMatch` except that it accepts `customizer` which\n * is invoked to compare values. If `customizer` returns `undefined` comparisons\n * are handled by the method instead. The `customizer` is invoked with three\n * arguments: (objValue, srcValue, index|key, object, source).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {Object} object The object to inspect.\n * @param {Object} source The object of property values to match.\n * @param {Function} [customizer] The function to customize comparisons.\n * @returns {boolean} Returns `true` if `object` is a match, else `false`.\n * @example\n *\n * function isGreeting(value) {\n * return /^h(?:i|ello)$/.test(value);\n * }\n *\n * function customizer(objValue, srcValue) {\n * if (isGreeting(objValue) && isGreeting(srcValue)) {\n * return true;\n * }\n * }\n *\n * var object = { 'greeting': 'hello' };\n * var source = { 'greeting': 'hi' };\n *\n * _.isMatchWith(object, source, customizer);\n * // => true\n */\n isMatchWith(object: Object, source: Object, customizer: isMatchWithCustomizer): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.isMatchWith\n */\n isMatchWith(source: Object, customizer: isMatchWithCustomizer): boolean;\n }\n\n //_.isNaN\n interface LoDashStatic {\n /**\n * Checks if value is NaN.\n *\n * Note: This method is not the same as isNaN which returns true for undefined and other non-numeric values.\n *\n * @param value The value to check.\n * @return Returns true if value is NaN, else false.\n */\n isNaN(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isNaN\n */\n isNaN(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isNaN\n */\n isNaN(): LoDashExplicitWrapper;\n }\n\n //_.isNative\n interface LoDashStatic {\n /**\n * Checks if value is a native function.\n * @param value The value to check.\n *\n * @retrun Returns true if value is a native function, else false.\n */\n isNative(value: any): value is Function;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNative\n */\n isNative(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNative\n */\n isNative(): LoDashExplicitWrapper;\n }\n\n //_.isNil\n interface LoDashStatic {\n /**\n * Checks if `value` is `null` or `undefined`.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is nullish, else `false`.\n * @example\n *\n * _.isNil(null);\n * // => true\n *\n * _.isNil(void 0);\n * // => true\n *\n * _.isNil(NaN);\n * // => false\n */\n isNil(value: any): value is null | undefined;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNil\n */\n isNil(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNil\n */\n isNil(): LoDashExplicitWrapper;\n }\n\n //_.isNull\n interface LoDashStatic {\n /**\n * Checks if value is null.\n *\n * @param value The value to check.\n * @return Returns true if value is null, else false.\n */\n isNull(value: any): value is null;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNull\n */\n isNull(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNull\n */\n isNull(): LoDashExplicitWrapper;\n }\n\n //_.isNumber\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Number primitive or object.\n *\n * Note: To exclude Infinity, -Infinity, and NaN, which are classified as numbers, use the _.isFinite method.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isNumber(value?: any): value is number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isNumber\n */\n isNumber(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isNumber\n */\n isNumber(): LoDashExplicitWrapper;\n }\n\n //_.isObject\n interface LoDashStatic {\n /**\n * Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0),\n * and new String(''))\n *\n * @param value The value to check.\n * @return Returns true if value is an object, else false.\n */\n isObject(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isObject\n */\n isObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isObject\n */\n isObject(): LoDashExplicitWrapper;\n }\n\n //_.isObjectLike\n interface LoDashStatic {\n /**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\n isObjectLike(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isObjectLike\n */\n isObjectLike(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isObjectLike\n */\n isObjectLike(): LoDashExplicitWrapper;\n }\n\n //_.isPlainObject\n interface LoDashStatic {\n /**\n * Checks if value is a plain object, that is, an object created by the Object constructor or one with a\n * [[Prototype]] of null.\n *\n * Note: This method assumes objects created by the Object constructor have no inherited enumerable properties.\n *\n * @param value The value to check.\n * @return Returns true if value is a plain object, else false.\n */\n isPlainObject(value?: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isPlainObject\n */\n isPlainObject(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isPlainObject\n */\n isPlainObject(): LoDashExplicitWrapper;\n }\n\n //_.isRegExp\n interface LoDashStatic {\n /**\n * Checks if value is classified as a RegExp object.\n * @param value The value to check.\n *\n * @return Returns true if value is correctly classified, else false.\n */\n isRegExp(value?: any): value is RegExp;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isRegExp\n */\n isRegExp(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isRegExp\n */\n isRegExp(): LoDashExplicitWrapper;\n }\n\n //_.isSafeInteger\n interface LoDashStatic {\n /**\n * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754\n * double precision number which isn't the result of a rounded unsafe integer.\n *\n * **Note:** This method is based on [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.\n * @example\n *\n * _.isSafeInteger(3);\n * // => true\n *\n * _.isSafeInteger(Number.MIN_VALUE);\n * // => false\n *\n * _.isSafeInteger(Infinity);\n * // => false\n *\n * _.isSafeInteger('3');\n * // => false\n */\n isSafeInteger(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isSafeInteger\n */\n isSafeInteger(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isSafeInteger\n */\n isSafeInteger(): LoDashExplicitWrapper;\n }\n\n //_.isSet\n interface LoDashStatic {\n /**\n * Checks if value is classified as a Set object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isSet(value?: any): value is Set;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isSet(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isSet(): LoDashExplicitWrapper;\n }\n\n //_.isString\n interface LoDashStatic {\n /**\n * Checks if value is classified as a String primitive or object.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isString(value?: any): value is string;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isString\n */\n isString(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isString\n */\n isString(): LoDashExplicitWrapper;\n }\n\n //_.isSymbol\n interface LoDashStatic {\n /**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\n isSymbol(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isSymbol\n */\n isSymbol(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isSymbol\n */\n isSymbol(): LoDashExplicitWrapper;\n }\n\n //_.isTypedArray\n interface LoDashStatic {\n /**\n * Checks if value is classified as a typed array.\n *\n * @param value The value to check.\n * @return Returns true if value is correctly classified, else false.\n */\n isTypedArray(value: any): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isTypedArray\n */\n isTypedArray(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isTypedArray\n */\n isTypedArray(): LoDashExplicitWrapper;\n }\n\n //_.isUndefined\n interface LoDashStatic {\n /**\n * Checks if value is undefined.\n *\n * @param value The value to check.\n * @return Returns true if value is undefined, else false.\n */\n isUndefined(value: any): value is undefined;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * see _.isUndefined\n */\n isUndefined(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * see _.isUndefined\n */\n isUndefined(): LoDashExplicitWrapper;\n }\n\n //_.isWeakMap\n interface LoDashStatic {\n /**\n * Checks if value is classified as a WeakMap object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isWeakMap(value?: any): value is WeakMap;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isWeakMap(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isSet\n */\n isWeakMap(): LoDashExplicitWrapper;\n }\n\n //_.isWeakSet\n interface LoDashStatic {\n /**\n * Checks if value is classified as a WeakSet object.\n *\n * @param value The value to check.\n * @returns Returns true if value is correctly classified, else false.\n */\n isWeakSet(value?: any): value is WeakSet;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.isWeakSet\n */\n isWeakSet(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.isWeakSet\n */\n isWeakSet(): LoDashExplicitWrapper;\n }\n\n //_.lt\n interface LoDashStatic {\n /**\n * Checks if value is less than other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is less than other, else false.\n */\n lt(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.lt\n */\n lt(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.lt\n */\n lt(other: any): LoDashExplicitWrapper;\n }\n\n //_.lte\n interface LoDashStatic {\n /**\n * Checks if value is less than or equal to other.\n *\n * @param value The value to compare.\n * @param other The other value to compare.\n * @return Returns true if value is less than or equal to other, else false.\n */\n lte(\n value: any,\n other: any\n ): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.lte\n */\n lte(other: any): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.lte\n */\n lte(other: any): LoDashExplicitWrapper;\n }\n\n //_.toArray\n interface LoDashStatic {\n /**\n * Converts value to an array.\n *\n * @param value The value to convert.\n * @return Returns the converted array.\n */\n toArray(value: List|Dictionary|NumericDictionary): T[];\n\n /**\n * @see _.toArray\n */\n toArray(value: TValue): TResult[];\n\n /**\n * @see _.toArray\n */\n toArray(value?: any): TResult[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.toArray\n */\n toArray(): LoDashExplicitArrayWrapper;\n }\n\n //_.toPlainObject\n interface LoDashStatic {\n /**\n * Converts value to a plain object flattening inherited enumerable properties of value to own properties\n * of the plain object.\n *\n * @param value The value to convert.\n * @return Returns the converted plain object.\n */\n toPlainObject(value?: any): TResult;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toPlainObject\n */\n toPlainObject(): LoDashImplicitObjectWrapper;\n }\n\n //_.toFinite\n interface LoDashStatic {\n /**\n * Converts `value` to a finite number.\n *\n * @static\n * @memberOf _\n * @since 4.12.0\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted number.\n * @example\n *\n * _.toFinite(3.2);\n * // => 3.2\n *\n * _.toFinite(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toFinite(Infinity);\n * // => 1.7976931348623157e+308\n *\n * _.toFinite('3.2');\n * // => 3.2\n */\n toFinite(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toFinite\n */\n toFinite(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toFinite\n */\n toFinite(): LoDashExplicitWrapper;\n }\n\n //_.toInteger\n interface LoDashStatic {\n /**\n * Converts `value` to an integer.\n *\n * **Note:** This function is loosely based on [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted integer.\n * @example\n *\n * _.toInteger(3);\n * // => 3\n *\n * _.toInteger(Number.MIN_VALUE);\n * // => 0\n *\n * _.toInteger(Infinity);\n * // => 1.7976931348623157e+308\n *\n * _.toInteger('3');\n * // => 3\n */\n toInteger(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toInteger\n */\n toInteger(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toInteger\n */\n toInteger(): LoDashExplicitWrapper;\n }\n\n //_.toLength\n interface LoDashStatic {\n /**\n * Converts `value` to an integer suitable for use as the length of an\n * array-like object.\n *\n * **Note:** This method is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @return {number} Returns the converted integer.\n * @example\n *\n * _.toLength(3);\n * // => 3\n *\n * _.toLength(Number.MIN_VALUE);\n * // => 0\n *\n * _.toLength(Infinity);\n * // => 4294967295\n *\n * _.toLength('3');\n * // => 3\n */\n toLength(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toLength\n */\n toLength(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toLength\n */\n toLength(): LoDashExplicitWrapper;\n }\n\n //_.toNumber\n interface LoDashStatic {\n /**\n * Converts `value` to a number.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to process.\n * @returns {number} Returns the number.\n * @example\n *\n * _.toNumber(3);\n * // => 3\n *\n * _.toNumber(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toNumber(Infinity);\n * // => Infinity\n *\n * _.toNumber('3');\n * // => 3\n */\n toNumber(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toNumber\n */\n toNumber(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toNumber\n */\n toNumber(): LoDashExplicitWrapper;\n }\n\n //_.toSafeInteger\n interface LoDashStatic {\n /**\n * Converts `value` to a safe integer. A safe integer can be compared and\n * represented correctly.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to convert.\n * @returns {number} Returns the converted integer.\n * @example\n *\n * _.toSafeInteger(3);\n * // => 3\n *\n * _.toSafeInteger(Number.MIN_VALUE);\n * // => 0\n *\n * _.toSafeInteger(Infinity);\n * // => 9007199254740991\n *\n * _.toSafeInteger('3');\n * // => 3\n */\n toSafeInteger(value: any): number;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toSafeInteger\n */\n toSafeInteger(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toSafeInteger\n */\n toSafeInteger(): LoDashExplicitWrapper;\n }\n\n //_.toString DUMMY\n interface LoDashStatic {\n /**\n * Converts `value` to a string if it's not one. An empty string is returned\n * for `null` and `undefined` values. The sign of `-0` is preserved.\n *\n * @static\n * @memberOf _\n * @category Lang\n * @param {*} value The value to process.\n * @returns {string} Returns the string.\n * @example\n *\n * _.toString(null);\n * // => ''\n *\n * _.toString(-0);\n * // => '-0'\n *\n * _.toString([1, 2, 3]);\n * // => '1,2,3'\n */\n toString(value: any): string;\n }\n\n /********\n * Math *\n ********/\n\n //_.add\n interface LoDashStatic {\n /**\n * Adds two numbers.\n *\n * @param augend The first number to add.\n * @param addend The second number to add.\n * @return Returns the sum.\n */\n add(\n augend: number,\n addend: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.add\n */\n add(addend: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.add\n */\n add(addend: number): LoDashExplicitWrapper;\n }\n\n //_.ceil\n interface LoDashStatic {\n /**\n * Calculates n rounded up to precision.\n *\n * @param n The number to round up.\n * @param precision The precision to round up to.\n * @return Returns the rounded up number.\n */\n ceil(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.ceil\n */\n ceil(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.ceil\n */\n ceil(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.divide\n interface LoDashStatic {\n /**\n * Divide two numbers.\n *\n * @param dividend The first number in a division.\n * @param divisor The second number in a division.\n * @returns Returns the quotient.\n */\n divide(\n dividend: number,\n divisor: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.divide\n */\n divide(divisor: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.divide\n */\n divide(divisor: number): LoDashExplicitWrapper;\n }\n\n //_.floor\n interface LoDashStatic {\n /**\n * Calculates n rounded down to precision.\n *\n * @param n The number to round down.\n * @param precision The precision to round down to.\n * @return Returns the rounded down number.\n */\n floor(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.floor\n */\n floor(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.floor\n */\n floor(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.max\n interface LoDashStatic {\n /**\n * Computes the maximum value of `array`. If `array` is empty or falsey\n * `undefined` is returned.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {*} Returns the maximum value.\n */\n max(\n collection: List | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.max\n */\n max(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.max\n */\n max(): T | undefined;\n }\n\n //_.maxBy\n interface LoDashStatic {\n /**\n * This method is like `_.max` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * the value is ranked. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {*} Returns the maximum value.\n * @example\n *\n * var objects = [{ 'n': 1 }, { 'n': 2 }];\n *\n * _.maxBy(objects, function(o) { return o.a; });\n * // => { 'n': 2 }\n *\n * // using the `_.property` iteratee shorthand\n * _.maxBy(objects, 'n');\n * // => { 'n': 2 }\n */\n maxBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n collection: List|Dictionary | null | undefined,\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: ListIterator|DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.maxBy\n */\n maxBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n //_.mean\n interface LoDashStatic {\n /**\n * Computes the mean of the values in `array`.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {number} Returns the mean.\n * @example\n *\n * _.mean([4, 2, 8, 6]);\n * // => 5\n */\n mean(\n collection: List | null | undefined\n ): number;\n }\n\n //_.meanBy\n interface LoDashStatic {\n /**\n * Computes the mean of the provided propties of the objects in the `array`\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the mean.\n * @example\n *\n * _.mean([{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }], 'n');\n * // => 5\n */\n meanBy(\n collection: List | null | undefined,\n iteratee?: ListIterator | string\n ): number;\n\n meanBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator | string\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.mean\n */\n mean(): number;\n\n /**\n * @see _.mean\n */\n mean(): number;\n }\n\n //_.min\n interface LoDashStatic {\n /**\n * Computes the minimum value of `array`. If `array` is empty or falsey\n * `undefined` is returned.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {*} Returns the minimum value.\n */\n min(\n collection: List | null | undefined\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.min\n */\n min(): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.min\n */\n min(): T | undefined;\n }\n\n //_.minBy\n interface LoDashStatic {\n /**\n * This method is like `_.min` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the criterion by which\n * the value is ranked. The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {*} Returns the minimum value.\n * @example\n *\n * var objects = [{ 'n': 1 }, { 'n': 2 }];\n *\n * _.minBy(objects, function(o) { return o.a; });\n * // => { 'n': 1 }\n *\n * // using the `_.property` iteratee shorthand\n * _.minBy(objects, 'n');\n * // => { 'n': 1 }\n */\n minBy(\n collection: List | null | undefined,\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: List|Dictionary | null | undefined,\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n collection: List|Dictionary | null | undefined,\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: ListIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: ListIterator|DictionaryIterator\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n iteratee?: string\n ): T | undefined;\n\n /**\n * @see _.minBy\n */\n minBy(\n whereValue?: TObject\n ): T | undefined;\n }\n\n //_.multiply\n interface LoDashStatic {\n /**\n * Multiply two numbers.\n * @param multiplier The first number in a multiplication.\n * @param multiplicand The second number in a multiplication.\n * @returns Returns the product.\n */\n multiply(\n multiplier: number,\n multiplicand: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.multiply\n */\n multiply(multiplicand: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.multiply\n */\n multiply(multiplicand: number): LoDashExplicitWrapper;\n }\n\n //_.round\n interface LoDashStatic {\n /**\n * Calculates n rounded to precision.\n *\n * @param n The number to round.\n * @param precision The precision to round to.\n * @return Returns the rounded number.\n */\n round(\n n: number,\n precision?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.round\n */\n round(precision?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.round\n */\n round(precision?: number): LoDashExplicitWrapper;\n }\n\n //_.sum\n interface LoDashStatic {\n /**\n * Computes the sum of the values in `array`.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @returns {number} Returns the sum.\n * @example\n *\n * _.sum([4, 2, 8, 6]);\n * // => 20\n */\n sum(collection: List | null | undefined): number;\n\n /**\n * @see _.sum\n */\n sum(collection: List|Dictionary | null | undefined): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sum\n */\n sum(): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sum\n **/\n sum(): number;\n\n /**\n * @see _.sum\n */\n sum(): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n\n /**\n * @see _.sum\n */\n sum(): LoDashExplicitWrapper;\n }\n\n //_.sumBy\n interface LoDashStatic {\n /**\n * This method is like `_.sum` except that it accepts `iteratee` which is\n * invoked for each element in `array` to generate the value to be summed.\n * The iteratee is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {Array} array The array to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {number} Returns the sum.\n * @example\n *\n * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];\n *\n * _.sumBy(objects, function(o) { return o.n; });\n * // => 20\n *\n * // using the `_.property` iteratee shorthand\n * _.sumBy(objects, 'n');\n * // => 20\n */\n sumBy(\n collection: List | null | undefined,\n iteratee: ListIterator\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List<{}> | null | undefined,\n iteratee: string\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List | null | undefined\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(\n collection: List<{}> | null | undefined,\n iteratee: Dictionary<{}>\n ): number;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): number;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator<{}, number>\n ): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): number;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): number;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.sumBy\n */\n sumBy(\n iteratee: ListIterator<{}, number>\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: string): LoDashExplicitWrapper;\n\n /**\n * @see _.sumBy\n */\n sumBy(iteratee: Dictionary<{}>): LoDashExplicitWrapper;\n }\n\n /**********\n * Number *\n **********/\n\n //_.subtract\n interface LoDashStatic {\n /**\n * Subtract two numbers.\n *\n * @static\n * @memberOf _\n * @category Math\n * @param {number} minuend The first number in a subtraction.\n * @param {number} subtrahend The second number in a subtraction.\n * @returns {number} Returns the difference.\n * @example\n *\n * _.subtract(6, 4);\n * // => 2\n */\n subtract(\n minuend: number,\n subtrahend: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.subtract\n */\n subtract(\n subtrahend: number\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.subtract\n */\n subtract(\n subtrahend: number\n ): LoDashExplicitWrapper;\n }\n\n //_.clamp\n interface LoDashStatic {\n /**\n * Clamps `number` within the inclusive `lower` and `upper` bounds.\n *\n * @static\n * @memberOf _\n * @category Number\n * @param {number} number The number to clamp.\n * @param {number} [lower] The lower bound.\n * @param {number} upper The upper bound.\n * @returns {number} Returns the clamped number.\n * @example\n *\n * _.clamp(-10, -5, 5);\n * // => -5\n *\n * _.clamp(10, -5, 5);\n * // => 5\n */\n clamp(\n number: number,\n lower: number,\n upper: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.clamp\n */\n clamp(\n lower: number,\n upper: number\n ): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.clamp\n */\n clamp(\n lower: number,\n upper: number\n ): LoDashExplicitWrapper;\n }\n\n //_.inRange\n interface LoDashStatic {\n /**\n * Checks if n is between start and up to but not including, end. If end is not specified it’s set to start\n * with start then set to 0.\n *\n * @param n The number to check.\n * @param start The start of the range.\n * @param end The end of the range.\n * @return Returns true if n is in the range, else false.\n */\n inRange(\n n: number,\n start: number,\n end: number\n ): boolean;\n\n /**\n * @see _.inRange\n */\n inRange(\n n: number,\n end: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.inRange\n */\n inRange(\n start: number,\n end: number\n ): boolean;\n\n /**\n * @see _.inRange\n */\n inRange(end: number): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.inRange\n */\n inRange(\n start: number,\n end: number\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.inRange\n */\n inRange(end: number): LoDashExplicitWrapper;\n }\n\n //_.random\n interface LoDashStatic {\n /**\n * Produces a random number between min and max (inclusive). If only one argument is provided a number between\n * 0 and the given number is returned. If floating is true, or either min or max are floats, a floating-point\n * number is returned instead of an integer.\n *\n * @param min The minimum possible value.\n * @param max The maximum possible value.\n * @param floating Specify returning a floating-point number.\n * @return Returns the random number.\n */\n random(\n min?: number,\n max?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(\n min?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.random\n */\n random(\n max?: number,\n floating?: boolean\n ): number;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.random\n */\n random(\n max?: number,\n floating?: boolean\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.random\n */\n random(floating?: boolean): LoDashExplicitWrapper;\n }\n\n /**********\n * Object *\n **********/\n\n //_.assign\n interface LoDashStatic {\n /**\n * Assigns own enumerable properties of source objects to the destination\n * object. Source objects are applied from left to right. Subsequent sources\n * overwrite property assignments of previous sources.\n *\n * **Note:** This method mutates `object` and is loosely based on\n * [`Object.assign`](https://mdn.io/Object/assign).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function Foo() {\n * this.c = 3;\n * }\n *\n * function Bar() {\n * this.e = 5;\n * }\n *\n * Foo.prototype.d = 4;\n * Bar.prototype.f = 6;\n *\n * _.assign({ 'a': 1 }, new Foo, new Bar);\n * // => { 'a': 1, 'c': 3, 'e': 5 }\n */\n assign(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assign\n */\n assign(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assign\n */\n assign(object: TObject): TObject;\n\n /**\n * @see _.assign\n */\n assign(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assign\n */\n assign(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assign\n */\n assign(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assign\n */\n assign(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assign\n */\n assign(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method is like `_.assign` except that it accepts `customizer` which\n * is invoked to produce the assigned values. If `customizer` returns `undefined`\n * assignment is handled by the method instead. The `customizer` is invoked\n * with five arguments: (objValue, srcValue, key, object, source).\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} [customizer] The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * return _.isUndefined(objValue) ? srcValue : objValue;\n * }\n *\n * var defaults = _.partialRight(_.assignWith, customizer);\n *\n * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });\n * // => { 'a': 1, 'b': 2 }\n */\n assignWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignWith\n */\n assignWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignWith\n */\n assignWith(object: TObject): TObject;\n\n /**\n * @see _.assignWith\n */\n assignWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignWith\n */\n assignWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignWith\n */\n assignWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignWith\n */\n assignWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignWith\n */\n assignWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.assignIn\n interface LoDashStatic {\n /**\n * This method is like `_.assign` except that it iterates over own and\n * inherited source properties.\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @alias extend\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function Foo() {\n * this.b = 2;\n * }\n *\n * function Bar() {\n * this.d = 4;\n * }\n *\n * Foo.prototype.c = 3;\n * Bar.prototype.e = 5;\n *\n * _.assignIn({ 'a': 1 }, new Foo, new Bar);\n * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }\n */\n assignIn(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignIn\n */\n assignIn(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignIn\n */\n assignIn(object: TObject): TObject;\n\n /**\n * @see _.assignIn\n */\n assignIn(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n assignIn(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n assignIn(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignIn\n */\n assignIn(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n assignIn(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.assignInWith\n type AssignCustomizer = (objectValue: any, sourceValue: any, key?: string, object?: {}, source?: {}) => any;\n\n interface LoDashStatic {\n /**\n * This method is like `_.assignIn` except that it accepts `customizer` which\n * is invoked to produce the assigned values. If `customizer` returns `undefined`\n * assignment is handled by the method instead. The `customizer` is invoked\n * with five arguments: (objValue, srcValue, key, object, source).\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @alias extendWith\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} [customizer] The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * return _.isUndefined(objValue) ? srcValue : objValue;\n * }\n *\n * var defaults = _.partialRight(_.assignInWith, customizer);\n *\n * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });\n * // => { 'a': 1, 'b': 2 }\n */\n assignInWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(object: TObject): TObject;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n assignInWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n assignInWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see assignInWith\n */\n assignInWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n assignInWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.create\n interface LoDashStatic {\n /**\n * Creates an object that inherits from the given prototype object. If a properties object is provided its own\n * enumerable properties are assigned to the created object.\n *\n * @param prototype The object to inherit from.\n * @param properties The properties to assign to the object.\n * @return Returns the new object.\n */\n create(\n prototype: T,\n properties?: U\n ): T & U;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.create\n */\n create(properties?: U): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.create\n */\n create(properties?: U): LoDashExplicitObjectWrapper;\n }\n\n //_.defaults\n interface LoDashStatic {\n /**\n * Assigns own enumerable properties of source object(s) to the destination object for all destination\n * properties that resolve to undefined. Once a property is set, additional values of the same property are\n * ignored.\n *\n * Note: This method mutates object.\n *\n * @param object The destination object.\n * @param sources The source objects.\n * @return The destination object.\n */\n defaults(\n object: TObject,\n source: TSource\n ): TSource & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TSource3 & TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TSource4 & TSource3 & TSource2 & TSource1 & TObject;\n\n /**\n * @see _.defaults\n */\n defaults(object: TObject): TObject;\n\n /**\n * @see _.defaults\n */\n defaults(\n object: any,\n ...sources: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defaults\n */\n defaults(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(...sources: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.defaults\n */\n defaults(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.defaults\n */\n defaults(...sources: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.defaultsDeep\n interface LoDashStatic {\n /**\n * This method is like _.defaults except that it recursively assigns default properties.\n * @param object The destination object.\n * @param sources The source objects.\n * @return Returns object.\n **/\n defaultsDeep(\n object: T,\n ...sources: any[]): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.defaultsDeep\n **/\n defaultsDeep(...sources: any[]): LoDashImplicitObjectWrapper;\n }\n\n // _.extend\n interface LoDashStatic {\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignIn\n */\n extend(object: TObject): TObject;\n\n /**\n * @see _.assignIn\n */\n extend(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n extend(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignIn\n */\n extend(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignIn\n */\n extend(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source: TSource,\n customizer: AssignCustomizer\n ): TObject & TSource;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.assignInWith\n */\n extendWith(object: TObject): TObject;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n extendWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(...otherArgs: any[]): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.assignInWith\n */\n extendWith(\n source: TSource,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: AssignCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.assignInWith\n */\n extendWith(...otherArgs: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.findKey\n interface LoDashStatic {\n /**\n * This method is like _.find except that it returns the key of the first element predicate returns truthy for\n * instead of the element itself.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param object The object to search.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the key of the matched element, else undefined.\n */\n findKey(\n object: TObject,\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n object: TObject,\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n object: TObject,\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey, TObject>(\n object: TObject,\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findKey\n */\n findKey>(\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: DictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey(\n predicate?: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findKey\n */\n findKey>(\n predicate?: TWhere\n ): LoDashExplicitWrapper;\n }\n\n //_.findLastKey\n interface LoDashStatic {\n /**\n * This method is like _.findKey except that it iterates over elements of a collection in the opposite order.\n *\n * If a property name is provided for predicate the created _.property style callback returns the property\n * value of the given element.\n *\n * If a value is also provided for thisArg the created _.matchesProperty style callback returns true for\n * elements that have a matching property value, else false.\n *\n * If an object is provided for predicate the created _.matches style callback returns true for elements that\n * have the properties of the given object, else false.\n *\n * @param object The object to search.\n * @param predicate The function invoked per iteration.\n * @param thisArg The this binding of predicate.\n * @return Returns the key of the matched element, else undefined.\n */\n findLastKey(\n object: TObject,\n predicate?: DictionaryIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n object: TObject,\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n object: TObject,\n predicate?: string\n ): string;\n\n /**\n * @see _.findLastKey\n */\n findLastKey, TObject>(\n object: TObject,\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: DictionaryIterator\n ): string;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: ObjectIterator\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: string\n ): string | undefined;\n\n /**\n * @see _.findLastKey\n */\n findLastKey>(\n predicate?: TWhere\n ): string | undefined;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: DictionaryIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: ObjectIterator\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey(\n predicate?: string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.findLastKey\n */\n findLastKey>(\n predicate?: TWhere\n ): LoDashExplicitWrapper;\n }\n\n //_.forIn\n interface LoDashStatic {\n /**\n * Iterates over own and inherited enumerable properties of an object invoking iteratee for each property. The\n * iteratee is bound to thisArg and invoked with three arguments: (value, key, object). Iteratee functions may\n * exit iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forIn(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forIn\n */\n forIn(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forIn\n */\n forIn(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forIn\n */\n forIn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forIn\n */\n forIn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forInRight\n interface LoDashStatic {\n /**\n * This method is like _.forIn except that it iterates over properties of object in the opposite order.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forInRight(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forInRight\n */\n forInRight(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forInRight\n */\n forInRight(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forInRight\n */\n forInRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forInRight\n */\n forInRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forOwn\n interface LoDashStatic {\n /**\n * Iterates over own enumerable properties of an object invoking iteratee for each property. The iteratee is\n * bound to thisArg and invoked with three arguments: (value, key, object). Iteratee functions may exit\n * iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forOwn(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forOwn\n */\n forOwn(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forOwn\n */\n forOwn(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forOwn\n */\n forOwn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forOwn\n */\n forOwn(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.forOwnRight\n interface LoDashStatic {\n /**\n * This method is like _.forOwn except that it iterates over properties of object in the opposite order.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns object.\n */\n forOwnRight(\n object: Dictionary,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary | null | undefined;\n\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n object: T,\n iteratee?: ObjectIterator\n ): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.forOwnRight\n */\n forOwnRight(\n iteratee?: DictionaryIterator\n ): TWrapper;\n }\n\n //_.functions\n interface LoDashStatic {\n /**\n * Creates an array of function property names from own enumerable properties\n * of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to inspect.\n * @returns {Array} Returns the new array of property names.\n * @example\n *\n * function Foo() {\n * this.a = _.constant('a');\n * this.b = _.constant('b');\n * }\n *\n * Foo.prototype.c = _.constant('c');\n *\n * _.functions(new Foo);\n * // => ['a', 'b']\n */\n functions(object: any): string[];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.functions\n */\n functions(): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.functions\n */\n functions(): _.LoDashExplicitArrayWrapper;\n }\n\n //_.functionsIn\n interface LoDashStatic {\n /**\n * Creates an array of function property names from own and inherited\n * enumerable properties of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to inspect.\n * @returns {Array} Returns the new array of property names.\n * @example\n *\n * function Foo() {\n * this.a = _.constant('a');\n * this.b = _.constant('b');\n * }\n *\n * Foo.prototype.c = _.constant('c');\n *\n * _.functionsIn(new Foo);\n * // => ['a', 'b', 'c']\n */\n functionsIn(object: any): string[];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.functionsIn\n */\n functionsIn(): _.LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.functionsIn\n */\n functionsIn(): _.LoDashExplicitArrayWrapper;\n }\n\n //_.get\n interface LoDashStatic {\n /**\n * Gets the property value at path of object. If the resolved value is undefined the defaultValue is used\n * in its place.\n *\n * @param object The object to query.\n * @param path The path of the property to get.\n * @param defaultValue The value returned if the resolved value is undefined.\n * @return Returns the resolved value.\n */\n get(\n object: TObject,\n path: Many,\n defaultValue?: TResult\n ): TResult;\n\n /**\n * @see _.get\n */\n get(\n object: any,\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: TResult\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.get\n */\n get(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n //_.has\n interface LoDashStatic {\n /**\n * Checks if `path` is a direct property of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @param {Array|string} path The path to check.\n * @returns {boolean} Returns `true` if `path` exists, else `false`.\n * @example\n *\n * var object = { 'a': { 'b': { 'c': 3 } } };\n * var other = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });\n *\n * _.has(object, 'a');\n * // => true\n *\n * _.has(object, 'a.b.c');\n * // => true\n *\n * _.has(object, ['a', 'b', 'c']);\n * // => true\n *\n * _.has(other, 'a');\n * // => false\n */\n has(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.has\n */\n has(path: Many): boolean;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.has\n */\n has(path: Many): LoDashExplicitWrapper;\n }\n\n //_.hasIn\n interface LoDashStatic {\n /**\n * Checks if `path` is a direct or inherited property of `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @param {Array|string} path The path to check.\n * @returns {boolean} Returns `true` if `path` exists, else `false`.\n * @example\n *\n * var object = _.create({ 'a': _.create({ 'b': _.create({ 'c': 3 }) }) });\n *\n * _.hasIn(object, 'a');\n * // => true\n *\n * _.hasIn(object, 'a.b.c');\n * // => true\n *\n * _.hasIn(object, ['a', 'b', 'c']);\n * // => true\n *\n * _.hasIn(object, 'b');\n * // => false\n */\n hasIn(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.hasIn\n */\n hasIn(path: Many): boolean;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.hasIn\n */\n hasIn(path: Many): LoDashExplicitWrapper;\n }\n\n //_.invert\n interface LoDashStatic {\n /**\n * Creates an object composed of the inverted keys and values of object. If object contains duplicate values,\n * subsequent values overwrite property assignments of previous values unless multiValue is true.\n *\n * @param object The object to invert.\n * @param multiValue Allow multiple values per key.\n * @return Returns the new inverted object.\n */\n invert(\n object: T,\n multiValue?: boolean\n ): TResult;\n\n /**\n * @see _.invert\n */\n invert(\n object: Object,\n multiValue?: boolean\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invert\n */\n invert(multiValue?: boolean): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invert\n */\n invert(multiValue?: boolean): LoDashExplicitObjectWrapper;\n }\n\n //_.inverBy\n type InvertByIterator = (value: T) => any;\n\n interface LoDashStatic {\n /**\n * This method is like _.invert except that the inverted object is generated from the results of running each\n * element of object through iteratee. The corresponding inverted value of each inverted key is an array of\n * keys responsible for generating the inverted value. The iteratee is invoked with one argument: (value).\n *\n * @param object The object to invert.\n * @param interatee The iteratee invoked per element.\n * @return Returns the new inverted object.\n */\n invertBy(\n object: Object,\n interatee?: InvertByIterator|string\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: _.Dictionary|_.NumericDictionary,\n interatee?: InvertByIterator|string\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: Object,\n interatee?: W\n ): Dictionary;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n object: _.Dictionary,\n interatee?: W\n ): Dictionary;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: InvertByIterator|string\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.invertBy\n */\n invertBy(\n interatee?: W\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.keys\n interface LoDashStatic {\n /**\n * Creates an array of the own enumerable property names of object.\n *\n * Note: Non-object values are coerced to objects. See the ES spec for more details.\n *\n * @param object The object to query.\n * @return Returns the array of property names.\n */\n keys(object?: any): string[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keys\n */\n keys(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keys\n */\n keys(): LoDashExplicitArrayWrapper;\n }\n\n //_.keysIn\n interface LoDashStatic {\n /**\n * Creates an array of the own and inherited enumerable property names of object.\n *\n * Note: Non-object values are coerced to objects.\n *\n * @param object The object to query.\n * @return An array of property names.\n */\n keysIn(object?: any): string[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.keysIn\n */\n keysIn(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.keysIn\n */\n keysIn(): LoDashExplicitArrayWrapper;\n }\n\n //_.mapKeys\n interface LoDashStatic {\n /**\n * The opposite of _.mapValues; this method creates an object with the same values as object and keys generated\n * by running each own enumerable property of object through iteratee.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param thisArg The this binding of iteratee.\n * @return Returns the new mapped object.\n */\n mapKeys(\n object: List | null | undefined,\n iteratee?: ListIterator\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: Dictionary | null | undefined,\n iteratee?: DictionaryIterator\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: List|Dictionary | null | undefined,\n iteratee?: TObject\n ): Dictionary;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n object: List|Dictionary | null | undefined,\n iteratee?: string\n ): Dictionary;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: ListIterator|DictionaryIterator\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: TObject\n ): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapKeys\n */\n mapKeys(\n iteratee?: string\n ): LoDashExplicitObjectWrapper>;\n }\n\n //_.mapValues\n interface LoDashStatic {\n /**\n * Creates an object with the same keys as object and values generated by running each own\n * enumerable property of object through iteratee. The iteratee function is bound to thisArg\n * and invoked with three arguments: (value, key, object).\n *\n * If a property name is provided iteratee the created \"_.property\" style callback returns\n * the property value of the given element.\n *\n * If a value is also provided for thisArg the creted \"_.matchesProperty\" style callback returns\n * true for elements that have a matching property value, else false;.\n *\n * If an object is provided for iteratee the created \"_.matches\" style callback returns true\n * for elements that have the properties of the given object, else false.\n *\n * @param {Object} object The object to iterate over.\n * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration.\n * @param {Object} [thisArg] The `this` binding of `iteratee`.\n * @return {Object} Returns the new mapped object.\n */\n mapValues(obj: Dictionary | null | undefined, callback: ObjectIterator): Dictionary;\n mapValues(obj: Dictionary | null | undefined, where: Dictionary): Dictionary;\n mapValues(obj: T | null | undefined, pluck: string): TMapped;\n mapValues(obj: T | null | undefined, callback: ObjectIterator): T;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.mapValues\n * TValue is the type of the property values of T.\n * TResult is the type output by the ObjectIterator function\n */\n mapValues(callback: ObjectIterator): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the property specified by pluck.\n * T should be a Dictionary>\n */\n mapValues(pluck: string): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the properties of each object in the values of T\n * T should be a Dictionary>\n */\n mapValues(where: Dictionary): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.mapValues\n * TValue is the type of the property values of T.\n * TResult is the type output by the ObjectIterator function\n */\n mapValues(callback: ObjectIterator): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the property specified by pluck.\n * T should be a Dictionary>\n */\n mapValues(pluck: string): LoDashExplicitObjectWrapper>;\n\n /**\n * @see _.mapValues\n * TResult is the type of the properties of each object in the values of T\n * T should be a Dictionary>\n */\n mapValues(where: Dictionary): LoDashExplicitObjectWrapper;\n }\n\n //_.merge\n interface LoDashStatic {\n /**\n * Recursively merges own and inherited enumerable properties of source\n * objects into the destination object, skipping source properties that resolve\n * to `undefined`. Array and plain object properties are merged recursively.\n * Other objects and value types are overridden by assignment. Source objects\n * are applied from left to right. Subsequent sources overwrite property\n * assignments of previous sources.\n *\n * **Note:** This method mutates `object`.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @example\n *\n * var users = {\n * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]\n * };\n *\n * var ages = {\n * 'data': [{ 'age': 36 }, { 'age': 40 }]\n * };\n *\n * _.merge(users, ages);\n * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }\n */\n merge(\n object: TObject,\n source: TSource\n ): TObject & TSource;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.merge\n */\n merge(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.merge\n */\n merge(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.merge\n */\n merge(\n source: TSource\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ...otherArgs: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.merge\n */\n merge(\n source: TSource\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.merge\n */\n merge(\n ...otherArgs: any[]\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.mergeWith\n type MergeWithCustomizer = (value: any, srcValue: any, key?: string, object?: Object, source?: Object) => any;\n\n interface LoDashStatic {\n /**\n * This method is like `_.merge` except that it accepts `customizer` which\n * is invoked to produce the merged values of the destination and source\n * properties. If `customizer` returns `undefined` merging is handled by the\n * method instead. The `customizer` is invoked with seven arguments:\n * (objValue, srcValue, key, object, source, stack).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} sources The source objects.\n * @param {Function} customizer The function to customize assigned values.\n * @returns {Object} Returns `object`.\n * @example\n *\n * function customizer(objValue, srcValue) {\n * if (_.isArray(objValue)) {\n * return objValue.concat(srcValue);\n * }\n * }\n *\n * var object = {\n * 'fruits': ['apple'],\n * 'vegetables': ['beet']\n * };\n *\n * var other = {\n * 'fruits': ['banana'],\n * 'vegetables': ['carrot']\n * };\n *\n * _.merge(object, other, customizer);\n * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }\n */\n mergeWith(\n object: TObject,\n source: TSource,\n customizer: MergeWithCustomizer\n ): TObject & TSource;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2 & TSource3;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: TObject,\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: MergeWithCustomizer\n ): TObject & TSource1 & TSource2 & TSource3 & TSource4;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n object: any,\n ...otherArgs: any[]\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source: TSource,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n source1: TSource1,\n source2: TSource2,\n source3: TSource3,\n source4: TSource4,\n customizer: MergeWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mergeWith\n */\n mergeWith(\n ...otherArgs: any[]\n ): LoDashImplicitObjectWrapper;\n }\n\n //_.omit\n interface LoDashStatic {\n /**\n * The opposite of `_.pick`; this method creates an object composed of the\n * own and inherited enumerable properties of `object` that are not omitted.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {...(string|string[])} [props] The property names to omit, specified\n * individually or in arrays..\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.omit(object, ['a', 'c']);\n * // => { 'b': '2' }\n */\n\n omit(\n object: T | null | undefined,\n ...predicate: Array>\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.omit\n */\n omit(\n ...predicate: Array>\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.omit\n */\n omit(\n ...predicate: Array>\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.omitBy\n interface LoDashStatic {\n /**\n * The opposite of `_.pickBy`; this method creates an object composed of the\n * own and inherited enumerable properties of `object` that `predicate`\n * doesn't return truthy for.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.omitBy(object, _.isNumber);\n * // => { 'b': '2' }\n */\n omitBy(\n object: T | null | undefined,\n predicate: ObjectIterator\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.omitBy\n */\n omitBy(\n predicate: ObjectIterator\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.omitBy\n */\n omitBy(\n predicate: ObjectIterator\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.pick\n interface LoDashStatic {\n /**\n * Creates an object composed of the picked `object` properties.\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {...(string|string[])} [props] The property names to pick, specified\n * individually or in arrays.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.pick(object, ['a', 'c']);\n * // => { 'a': 1, 'c': 3 }\n */\n pick(\n object: T | null | undefined,\n ...predicate: Array>\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.pick\n */\n pick(\n ...predicate: Array>\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.pick\n */\n pick(\n ...predicate: Array>\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.pickBy\n interface LoDashStatic {\n /**\n * Creates an object composed of the `object` properties `predicate` returns\n * truthy for. The predicate is invoked with one argument: (value).\n *\n * @static\n * @memberOf _\n * @category Object\n * @param {Object} object The source object.\n * @param {Function|Object|string} [predicate=_.identity] The function invoked per property.\n * @returns {Object} Returns the new object.\n * @example\n *\n * var object = { 'a': 1, 'b': '2', 'c': 3 };\n *\n * _.pickBy(object, _.isNumber);\n * // => { 'a': 1, 'c': 3 }\n */\n pickBy(\n object: T | null | undefined,\n predicate?: ObjectIterator\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.pickBy\n */\n pickBy(\n predicate?: ObjectIterator\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.pickBy\n */\n pickBy(\n predicate?: ObjectIterator\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.result\n interface LoDashStatic {\n /**\n * This method is like _.get except that if the resolved value is a function it’s invoked with the this binding\n * of its parent object and its result is returned.\n *\n * @param object The object to query.\n * @param path The path of the property to resolve.\n * @param defaultValue The value returned if the resolved value is undefined.\n * @return Returns the resolved value.\n */\n result(\n object: TObject,\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n\n /**\n * @see _.result\n */\n result(\n object: any,\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: TResult|((...args: any[]) => TResult)\n ): TResult;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.result\n */\n result(\n path: Many,\n defaultValue?: any\n ): TResultWrapper;\n }\n\n //_.set\n interface LoDashStatic {\n /**\n * Sets the value at path of object. If a portion of path doesn’t exist it’s created. Arrays are created for\n * missing index properties while objects are created for all other missing properties. Use _.setWith to\n * customize path creation.\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @return Returns object.\n */\n set(\n object: Object,\n path: Many,\n value: any\n ): TResult;\n\n /**\n * @see _.set\n */\n set(\n object: Object,\n path: Many,\n value: V\n ): TResult;\n\n /**\n * @see _.set\n */\n set(\n object: O,\n path: Many,\n value: V\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: any\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: V\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: any\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.set\n */\n set(\n path: Many,\n value: V\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.setWith\n type SetWithCustomizer = (nsValue: any, key: string, nsObject: T) => any;\n\n interface LoDashStatic {\n /**\n * This method is like _.set except that it accepts customizer which is invoked to produce the objects of\n * path. If customizer returns undefined path creation is handled by the method instead. The customizer is\n * invoked with three arguments: (nsValue, key, nsObject).\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param value The value to set.\n * @parem customizer The function to customize assigned values.\n * @return Returns object.\n */\n setWith(\n object: Object,\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): TResult;\n\n /**\n * @see _.setWith\n */\n setWith(\n object: Object,\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): TResult;\n\n /**\n * @see _.setWith\n */\n setWith(\n object: O,\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: any,\n customizer?: SetWithCustomizer\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.setWith\n */\n setWith(\n path: Many,\n value: V,\n customizer?: SetWithCustomizer\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.toPairs\n interface LoDashStatic {\n /**\n * Creates an array of own enumerable key-value pairs for object.\n *\n * @param object The object to query.\n * @return Returns the new array of key-value pairs.\n */\n toPairs(object?: T): [string, any][];\n\n toPairs(object?: T): [string, TResult][];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.toPairs\n */\n toPairs(): LoDashImplicitArrayWrapper<[string, TResult]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.toPairs\n */\n toPairs(): LoDashExplicitArrayWrapper<[string, TResult]>;\n }\n\n //_.toPairsIn\n interface LoDashStatic {\n /**\n * Creates an array of own and inherited enumerable key-value pairs for object.\n *\n * @param object The object to query.\n * @return Returns the new array of key-value pairs.\n */\n toPairsIn(object?: T): [string, any][];\n\n toPairsIn(object?: T): [string, TResult][];\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.toPairsIn\n */\n toPairsIn(): LoDashImplicitArrayWrapper<[string, TResult]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.toPairsIn\n */\n toPairsIn(): LoDashExplicitArrayWrapper<[string, TResult]>;\n }\n\n //_.transform\n interface LoDashStatic {\n /**\n * An alternative to _.reduce; this method transforms object to a new accumulator object which is the result of\n * running each of its own enumerable properties through iteratee, with each invocation potentially mutating\n * the accumulator object. The iteratee is bound to thisArg and invoked with four arguments: (accumulator,\n * value, key, object). Iteratee functions may exit iteration early by explicitly returning false.\n *\n * @param object The object to iterate over.\n * @param iteratee The function invoked per iteration.\n * @param accumulator The custom accumulator value.\n * @param thisArg The this binding of iteratee.\n * @return Returns the accumulated value.\n */\n transform(\n object: T[],\n iteratee?: MemoVoidArrayIterator,\n accumulator?: TResult[]\n ): TResult[];\n\n /**\n * @see _.transform\n */\n transform(\n object: T[],\n iteratee?: MemoVoidArrayIterator>,\n accumulator?: Dictionary\n ): Dictionary;\n\n /**\n * @see _.transform\n */\n transform(\n object: Dictionary,\n iteratee?: MemoVoidDictionaryIterator>,\n accumulator?: Dictionary\n ): Dictionary;\n\n /**\n * @see _.transform\n */\n transform(\n object: Dictionary,\n iteratee?: MemoVoidDictionaryIterator,\n accumulator?: TResult[]\n ): TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidArrayIterator,\n accumulator?: TResult[]\n ): LoDashImplicitArrayWrapper;\n\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidArrayIterator>,\n accumulator?: Dictionary\n ): LoDashImplicitObjectWrapper>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidDictionaryIterator>,\n accumulator?: Dictionary\n ): LoDashImplicitObjectWrapper>;\n\n /**\n * @see _.transform\n */\n transform(\n iteratee?: MemoVoidDictionaryIterator,\n accumulator?: TResult[]\n ): LoDashImplicitArrayWrapper;\n }\n\n //_.unset\n interface LoDashStatic {\n /**\n * Removes the property at path of object.\n *\n * Note: This method mutates object.\n *\n * @param object The object to modify.\n * @param path The path of the property to unset.\n * @return Returns true if the property is deleted, else false.\n */\n unset(\n object: T,\n path: Many\n ): boolean;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.unset\n */\n unset(path: Many): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.unset\n */\n unset(path: Many): LoDashExplicitWrapper;\n }\n\n //_.update\n interface LoDashStatic {\n /**\n * This method is like _.set except that accepts updater to produce the value to set. Use _.updateWith to\n * customize path creation. The updater is invoked with one argument: (value).\n *\n * @param object The object to modify.\n * @param path The path of the property to set.\n * @param updater The function to produce the updated value.\n * @return Returns object.\n */\n update(\n object: Object,\n path: Many,\n updater: Function\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: Object,\n path: Many,\n updater: U\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: O,\n path: Many,\n updater: Function\n ): TResult;\n\n /**\n * @see _.update\n */\n update(\n object: O,\n path: Many,\n updater: U\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: any\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: U\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: any\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.update\n */\n update(\n path: Many,\n updater: U\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.values\n interface LoDashStatic {\n /**\n * Creates an array of the own enumerable property values of object.\n *\n * @param object The object to query.\n * @return Returns an array of property values.\n */\n values(object?: Dictionary|NumericDictionary|List | null | undefined): T[];\n\n /**\n * @see _.values\n */\n values(object?: any): T[];\n }\n\n interface LoDashImplicitStringWrapper {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.values\n */\n values(): LoDashExplicitArrayWrapper;\n }\n\n //_.valuesIn\n interface LoDashStatic {\n /**\n * Creates an array of the own and inherited enumerable property values of object.\n *\n * @param object The object to query.\n * @return Returns the array of property values.\n */\n valuesIn(object?: Dictionary): T[];\n\n /**\n * @see _.valuesIn\n */\n valuesIn(object?: any): T[];\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.valuesIn\n */\n valuesIn(): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.valuesIn\n */\n valuesIn(): LoDashExplicitArrayWrapper;\n }\n\n /**********\n * String *\n **********/\n\n //_.camelCase\n interface LoDashStatic {\n /**\n * Converts string to camel case.\n *\n * @param string The string to convert.\n * @return Returns the camel cased string.\n */\n camelCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.camelCase\n */\n camelCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.camelCase\n */\n camelCase(): LoDashExplicitWrapper;\n }\n\n //_.capitalize\n interface LoDashStatic {\n /**\n * Converts the first character of string to upper case and the remaining to lower case.\n *\n * @param string The string to capitalize.\n * @return Returns the capitalized string.\n */\n capitalize(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.capitalize\n */\n capitalize(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.capitalize\n */\n capitalize(): LoDashExplicitWrapper;\n }\n\n //_.deburr\n interface LoDashStatic {\n /**\n * Deburrs string by converting latin-1 supplementary letters to basic latin letters and removing combining\n * diacritical marks.\n *\n * @param string The string to deburr.\n * @return Returns the deburred string.\n */\n deburr(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.deburr\n */\n deburr(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.deburr\n */\n deburr(): LoDashExplicitWrapper;\n }\n\n //_.endsWith\n interface LoDashStatic {\n /**\n * Checks if string ends with the given target string.\n *\n * @param string The string to search.\n * @param target The string to search for.\n * @param position The position to search from.\n * @return Returns true if string ends with target, else false.\n */\n endsWith(\n string?: string,\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.endsWith\n */\n endsWith(\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.endsWith\n */\n endsWith(\n target?: string,\n position?: number\n ): LoDashExplicitWrapper;\n }\n\n // _.escape\n interface LoDashStatic {\n /**\n * Converts the characters \"&\", \"<\", \">\", '\"', \"'\", and \"`\" in string to their corresponding HTML entities.\n *\n * Note: No other characters are escaped. To escape additional characters use a third-party library like he.\n *\n * hough the \">\" character is escaped for symmetry, characters like \">\" and \"/\" don’t need escaping in HTML\n * and have no special meaning unless they're part of a tag or unquoted attribute value. See Mathias Bynens’s\n * article (under \"semi-related fun fact\") for more details.\n *\n * Backticks are escaped because in IE < 9, they can break out of attribute values or HTML comments. See #59,\n * #102, #108, and #133 of the HTML5 Security Cheatsheet for more details.\n *\n * When working with HTML you should always quote attribute values to reduce XSS vectors.\n *\n * @param string The string to escape.\n * @return Returns the escaped string.\n */\n escape(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.escape\n */\n escape(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.escape\n */\n escape(): LoDashExplicitWrapper;\n }\n\n // _.escapeRegExp\n interface LoDashStatic {\n /**\n * Escapes the RegExp special characters \"^\", \"$\", \"\\\", \".\", \"*\", \"+\", \"?\", \"(\", \")\", \"[\", \"]\",\n * \"{\", \"}\", and \"|\" in string.\n *\n * @param string The string to escape.\n * @return Returns the escaped string.\n */\n escapeRegExp(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.escapeRegExp\n */\n escapeRegExp(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.escapeRegExp\n */\n escapeRegExp(): LoDashExplicitWrapper;\n }\n\n //_.kebabCase\n interface LoDashStatic {\n /**\n * Converts string to kebab case.\n *\n * @param string The string to convert.\n * @return Returns the kebab cased string.\n */\n kebabCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.kebabCase\n */\n kebabCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.kebabCase\n */\n kebabCase(): LoDashExplicitWrapper;\n }\n\n //_.lowerCase\n interface LoDashStatic {\n /**\n * Converts `string`, as space separated words, to lower case.\n *\n * @param string The string to convert.\n * @return Returns the lower cased string.\n */\n lowerCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.lowerCase\n */\n lowerCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.lowerCase\n */\n lowerCase(): LoDashExplicitWrapper;\n }\n\n //_.lowerFirst\n interface LoDashStatic {\n /**\n * Converts the first character of `string` to lower case.\n *\n * @param string The string to convert.\n * @return Returns the converted string.\n */\n lowerFirst(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.lowerFirst\n */\n lowerFirst(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.lowerFirst\n */\n lowerFirst(): LoDashExplicitWrapper;\n }\n\n //_.pad\n interface LoDashStatic {\n /**\n * Pads string on the left and right sides if it’s shorter than length. Padding characters are truncated if\n * they can’t be evenly divided by length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n pad(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.pad\n */\n pad(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.pad\n */\n pad(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.padEnd\n interface LoDashStatic {\n /**\n * Pads string on the right side if it’s shorter than length. Padding characters are truncated if they exceed\n * length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n padEnd(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.padEnd\n */\n padEnd(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.padEnd\n */\n padEnd(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.padStart\n interface LoDashStatic {\n /**\n * Pads string on the left side if it’s shorter than length. Padding characters are truncated if they exceed\n * length.\n *\n * @param string The string to pad.\n * @param length The padding length.\n * @param chars The string used as padding.\n * @return Returns the padded string.\n */\n padStart(\n string?: string,\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.padStart\n */\n padStart(\n length?: number,\n chars?: string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.padStart\n */\n padStart(\n length?: number,\n chars?: string\n ): LoDashExplicitWrapper;\n }\n\n //_.parseInt\n interface LoDashStatic {\n /**\n * Converts string to an integer of the specified radix. If radix is undefined or 0, a radix of 10 is used\n * unless value is a hexadecimal, in which case a radix of 16 is used.\n *\n * Note: This method aligns with the ES5 implementation of parseInt.\n *\n * @param string The string to convert.\n * @param radix The radix to interpret value by.\n * @return Returns the converted integer.\n */\n parseInt(\n string: string,\n radix?: number\n ): number;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.parseInt\n */\n parseInt(radix?: number): number;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.parseInt\n */\n parseInt(radix?: number): LoDashExplicitWrapper;\n }\n\n //_.repeat\n interface LoDashStatic {\n /**\n * Repeats the given string n times.\n *\n * @param string The string to repeat.\n * @param n The number of times to repeat the string.\n * @return Returns the repeated string.\n */\n repeat(\n string?: string,\n n?: number\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.repeat\n */\n repeat(n?: number): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.repeat\n */\n repeat(n?: number): LoDashExplicitWrapper;\n }\n\n //_.replace\n interface LoDashStatic {\n /**\n * Replaces matches for pattern in string with replacement.\n *\n * Note: This method is based on String#replace.\n *\n * @param string\n * @param pattern\n * @param replacement\n * @return Returns the modified string.\n */\n replace(\n string: string,\n pattern: RegExp|string,\n replacement: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): string;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.replace\n */\n replace(\n pattern?: RegExp|string,\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n\n /**\n * @see _.replace\n */\n replace(\n replacement?: Function|string\n ): LoDashExplicitWrapper;\n }\n\n //_.snakeCase\n interface LoDashStatic {\n /**\n * Converts string to snake case.\n *\n * @param string The string to convert.\n * @return Returns the snake cased string.\n */\n snakeCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.snakeCase\n */\n snakeCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.snakeCase\n */\n snakeCase(): LoDashExplicitWrapper;\n }\n\n //_.split\n interface LoDashStatic {\n /**\n * Splits string by separator.\n *\n * Note: This method is based on String#split.\n *\n * @param string\n * @param separator\n * @param limit\n * @return Returns the new array of string segments.\n */\n split(\n string: string,\n separator?: RegExp|string,\n limit?: number\n ): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.split\n */\n split(\n separator?: RegExp|string,\n limit?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.split\n */\n split(\n separator?: RegExp|string,\n limit?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.startCase\n interface LoDashStatic {\n /**\n * Converts string to start case.\n *\n * @param string The string to convert.\n * @return Returns the start cased string.\n */\n startCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.startCase\n */\n startCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.startCase\n */\n startCase(): LoDashExplicitWrapper;\n }\n\n //_.startsWith\n interface LoDashStatic {\n /**\n * Checks if string starts with the given target string.\n *\n * @param string The string to search.\n * @param target The string to search for.\n * @param position The position to search from.\n * @return Returns true if string starts with target, else false.\n */\n startsWith(\n string?: string,\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.startsWith\n */\n startsWith(\n target?: string,\n position?: number\n ): boolean;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.startsWith\n */\n startsWith(\n target?: string,\n position?: number\n ): LoDashExplicitWrapper;\n }\n\n //_.template\n interface TemplateOptions extends TemplateSettings {\n /**\n * The sourceURL of the template's compiled source.\n */\n sourceURL?: string;\n }\n\n interface TemplateExecutor {\n (data?: Object): string;\n source: string;\n }\n\n interface LoDashStatic {\n /**\n * Creates a compiled template function that can interpolate data properties in \"interpolate\" delimiters,\n * HTML-escape interpolated data properties in \"escape\" delimiters, and execute JavaScript in \"evaluate\"\n * delimiters. Data properties may be accessed as free variables in the template. If a setting object is\n * provided it takes precedence over _.templateSettings values.\n *\n * Note: In the development build _.template utilizes\n * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier\n * debugging.\n *\n * For more information on precompiling templates see\n * [lodash's custom builds documentation](https://lodash.com/custom-builds).\n *\n * For more information on Chrome extension sandboxes see\n * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).\n *\n * @param string The template string.\n * @param options The options object.\n * @param options.escape The HTML \"escape\" delimiter.\n * @param options.evaluate The \"evaluate\" delimiter.\n * @param options.imports An object to import into the template as free variables.\n * @param options.interpolate The \"interpolate\" delimiter.\n * @param options.sourceURL The sourceURL of the template's compiled source.\n * @param options.variable The data object variable name.\n * @return Returns the compiled template function.\n */\n template(\n string: string,\n options?: TemplateOptions\n ): TemplateExecutor;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.template\n */\n template(options?: TemplateOptions): TemplateExecutor;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.template\n */\n template(options?: TemplateOptions): LoDashExplicitObjectWrapper;\n }\n\n //_.toLower\n interface LoDashStatic {\n /**\n * Converts `string`, as a whole, to lower case.\n *\n * @param string The string to convert.\n * @return Returns the lower cased string.\n */\n toLower(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toLower\n */\n toLower(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toLower\n */\n toLower(): LoDashExplicitWrapper;\n }\n\n //_.toUpper\n interface LoDashStatic {\n /**\n * Converts `string`, as a whole, to upper case.\n *\n * @param string The string to convert.\n * @return Returns the upper cased string.\n */\n toUpper(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.toUpper\n */\n toUpper(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.toUpper\n */\n toUpper(): LoDashExplicitWrapper;\n }\n\n //_.trim\n interface LoDashStatic {\n /**\n * Removes leading and trailing whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trim(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trim\n */\n trim(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trim\n */\n trim(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.trimEnd\n interface LoDashStatic {\n /**\n * Removes trailing whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trimEnd(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trimEnd\n */\n trimEnd(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trimEnd\n */\n trimEnd(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.trimStart\n interface LoDashStatic {\n /**\n * Removes leading whitespace or specified characters from string.\n *\n * @param string The string to trim.\n * @param chars The characters to trim.\n * @return Returns the trimmed string.\n */\n trimStart(\n string?: string,\n chars?: string\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.trimStart\n */\n trimStart(chars?: string): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.trimStart\n */\n trimStart(chars?: string): LoDashExplicitWrapper;\n }\n\n //_.truncate\n interface TruncateOptions {\n /** The maximum string length. */\n length?: number;\n /** The string to indicate text is omitted. */\n omission?: string;\n /** The separator pattern to truncate to. */\n separator?: string|RegExp;\n }\n\n interface LoDashStatic {\n /**\n * Truncates string if it’s longer than the given maximum string length. The last characters of the truncated\n * string are replaced with the omission string which defaults to \"…\".\n *\n * @param string The string to truncate.\n * @param options The options object or maximum string length.\n * @return Returns the truncated string.\n */\n truncate(\n string?: string,\n options?: TruncateOptions\n ): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.truncate\n */\n truncate(options?: TruncateOptions): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.truncate\n */\n truncate(options?: TruncateOptions): LoDashExplicitWrapper;\n }\n\n //_.unescape\n interface LoDashStatic {\n /**\n * The inverse of _.escape; this method converts the HTML entities &, <, >, ", ', and `\n * in string to their corresponding characters.\n *\n * Note: No other HTML entities are unescaped. To unescape additional HTML entities use a third-party library\n * like he.\n *\n * @param string The string to unescape.\n * @return Returns the unescaped string.\n */\n unescape(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.unescape\n */\n unescape(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.unescape\n */\n unescape(): LoDashExplicitWrapper;\n }\n\n //_.upperCase\n interface LoDashStatic {\n /**\n * Converts `string`, as space separated words, to upper case.\n *\n * @param string The string to convert.\n * @return Returns the upper cased string.\n */\n upperCase(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.upperCase\n */\n upperCase(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.upperCase\n */\n upperCase(): LoDashExplicitWrapper;\n }\n\n //_.upperFirst\n interface LoDashStatic {\n /**\n * Converts the first character of `string` to upper case.\n *\n * @param string The string to convert.\n * @return Returns the converted string.\n */\n upperFirst(string?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.upperFirst\n */\n upperFirst(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.upperFirst\n */\n upperFirst(): LoDashExplicitWrapper;\n }\n\n //_.words\n interface LoDashStatic {\n /**\n * Splits `string` into an array of its words.\n *\n * @param string The string to inspect.\n * @param pattern The pattern to match words.\n * @return Returns the words of `string`.\n */\n words(\n string?: string,\n pattern?: string|RegExp\n ): string[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.words\n */\n words(pattern?: string|RegExp): string[];\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.words\n */\n words(pattern?: string|RegExp): LoDashExplicitArrayWrapper;\n }\n\n /***********\n * Utility *\n ***********/\n\n //_.attempt\n interface LoDashStatic {\n /**\n * Attempts to invoke func, returning either the result or the caught error object. Any additional arguments\n * are provided to func when it’s invoked.\n *\n * @param func The function to attempt.\n * @return Returns the func result or error object.\n */\n attempt(func: (...args: any[]) => TResult, ...args: any[]): TResult|Error;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.attempt\n */\n attempt(...args: any[]): TResult|Error;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.attempt\n */\n attempt(...args: any[]): LoDashExplicitObjectWrapper;\n }\n\n //_.constant\n interface LoDashStatic {\n /**\n * Creates a function that returns value.\n *\n * @param value The value to return from the new function.\n * @return Returns the new function.\n */\n constant(value: T): () => T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.constant\n */\n constant(): LoDashImplicitObjectWrapper<() => TResult>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.constant\n */\n constant(): LoDashExplicitObjectWrapper<() => TResult>;\n }\n\n //_.defaultTo\n interface LoDashStatic {\n /**\n * Checks `value` to determine whether a default value should be returned in\n * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,\n * or `undefined`.\n *\n * @param value The value to check.\n * @param defaultValue The default value.\n * @returns Returns the resolved value.\n */\n defaultTo(value: T | null | undefined, defaultValue: T): T;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.defaultTo\n */\n defaultTo(value: TResult): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.defaultTo\n */\n defaultTo(value: TResult): LoDashExplicitObjectWrapper;\n }\n\n //_.identity\n interface LoDashStatic {\n /**\n * This method returns the first argument provided to it.\n *\n * @param value Any value.\n * @return Returns value.\n */\n identity(value: T): T;\n\n /**\n * @see _.identity\n */\n identity(): undefined;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.identity\n */\n identity(): T;\n }\n\n interface LoDashImplicitArrayWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TArray;\n }\n\n interface LoDashImplicitObjectWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TObject;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.identity\n */\n identity(): LoDashExplicitWrapper;\n }\n\n interface LoDashExplicitArrayWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TWrapper;\n }\n\n interface LoDashExplicitObjectWrapperBase {\n /**\n * @see _.identity\n */\n identity(): TWrapper;\n }\n\n //_.iteratee\n interface LoDashStatic {\n /**\n * Creates a function that invokes `func` with the arguments of the created\n * function. If `func` is a property name the created callback returns the\n * property value for a given element. If `func` is an object the created\n * callback returns `true` for elements that contain the equivalent object properties, otherwise it returns `false`.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {*} [func=_.identity] The value to convert to a callback.\n * @returns {Function} Returns the callback.\n * @example\n *\n * var users = [\n * { 'user': 'barney', 'age': 36 },\n * { 'user': 'fred', 'age': 40 }\n * ];\n *\n * // create custom iteratee shorthands\n * _.iteratee = _.wrap(_.iteratee, function(callback, func) {\n * var p = /^(\\S+)\\s*([<>])\\s*(\\S+)$/.exec(func);\n * return !p ? callback(func) : function(object) {\n * return (p[2] == '>' ? object[p[1]] > p[3] : object[p[1]] < p[3]);\n * };\n * });\n *\n * _.filter(users, 'age > 36');\n * // => [{ 'user': 'fred', 'age': 40 }]\n */\n iteratee(\n func: TFunction\n ): TFunction;\n\n /**\n * @see _.iteratee\n */\n iteratee(\n func: string\n ): (object: any) => TResult;\n\n /**\n * @see _.iteratee\n */\n iteratee(\n func: Object\n ): (object: any) => boolean;\n\n /**\n * @see _.iteratee\n */\n iteratee(): (value: TResult) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(object: any) => boolean>;\n\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashImplicitObjectWrapper<(...args: any[]) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(object: any) => boolean>;\n\n /**\n * @see _.iteratee\n */\n iteratee(): LoDashExplicitObjectWrapper<(...args: any[]) => TResult>;\n }\n\n //_.matches\n interface LoDashStatic {\n /**\n * Creates a function that performs a deep comparison between a given object and source, returning true if the\n * given object has equivalent property values, else false.\n *\n * Note: This method supports comparing arrays, booleans, Date objects, numbers, Object objects, regexes, and\n * strings. Objects are compared by their own, not inherited, enumerable properties. For comparing a single own\n * or inherited property value see _.matchesProperty.\n *\n * @param source The object of property values to match.\n * @return Returns the new function.\n */\n matches(source: T): (value: any) => boolean;\n\n /**\n * @see _.matches\n */\n matches(source: T): (value: V) => boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.matches\n */\n matches(): LoDashImplicitObjectWrapper<(value: V) => boolean>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.matches\n */\n matches(): LoDashExplicitObjectWrapper<(value: V) => boolean>;\n }\n\n //_.matchesProperty\n interface LoDashStatic {\n /**\n * Creates a function that compares the property value of path on a given object to value.\n *\n * Note: This method supports comparing arrays, booleans, Date objects, numbers, Object objects, regexes, and\n * strings. Objects are compared by their own, not inherited, enumerable properties.\n *\n * @param path The path of the property to get.\n * @param srcValue The value to match.\n * @return Returns the new function.\n */\n matchesProperty(\n path: Many,\n srcValue: T\n ): (value: any) => boolean;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n path: Many,\n srcValue: T\n ): (value: V) => boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashImplicitObjectWrapper<(value: any) => boolean>;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashImplicitObjectWrapper<(value: Value) => boolean>;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashExplicitObjectWrapper<(value: any) => boolean>;\n\n /**\n * @see _.matchesProperty\n */\n matchesProperty(\n srcValue: SrcValue\n ): LoDashExplicitObjectWrapper<(value: Value) => boolean>;\n }\n\n //_.method\n interface LoDashStatic {\n /**\n * Creates a function that invokes the method at path on a given object. Any additional arguments are provided\n * to the invoked method.\n *\n * @param path The path of the method to invoke.\n * @param args The arguments to invoke the method with.\n * @return Returns the new function.\n */\n method(\n path: string|StringRepresentable[],\n ...args: any[]\n ): (object: TObject) => TResult;\n\n /**\n * @see _.method\n */\n method(\n path: string|StringRepresentable[],\n ...args: any[]\n ): (object: any) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashImplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: TObject) => TResult>;\n\n /**\n * @see _.method\n */\n method(...args: any[]): LoDashExplicitObjectWrapper<(object: any) => TResult>;\n }\n\n //_.methodOf\n interface LoDashStatic {\n /**\n * The opposite of _.method; this method creates a function that invokes the method at a given path on object.\n * Any additional arguments are provided to the invoked method.\n *\n * @param object The object to query.\n * @param args The arguments to invoke the method with.\n * @return Returns the new function.\n */\n methodOf(\n object: TObject,\n ...args: any[]\n ): (path: Many) => TResult;\n\n /**\n * @see _.methodOf\n */\n methodOf(\n object: {},\n ...args: any[]\n ): (path: Many) => TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.methodOf\n */\n methodOf(\n ...args: any[]\n ): LoDashImplicitObjectWrapper<(path: Many) => TResult>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.methodOf\n */\n methodOf(\n ...args: any[]\n ): LoDashExplicitObjectWrapper<(path: Many) => TResult>;\n }\n\n //_.mixin\n interface MixinOptions {\n chain?: boolean;\n }\n\n interface LoDashStatic {\n /**\n * Adds all own enumerable function properties of a source object to the destination object. If object is a\n * function then methods are added to its prototype as well.\n *\n * Note: Use _.runInContext to create a pristine lodash function to avoid conflicts caused by modifying\n * the original.\n *\n * @param object The destination object.\n * @param source The object of functions to add.\n * @param options The options object.\n * @param options.chain Specify whether the functions added are chainable.\n * @return Returns object.\n */\n mixin(\n object: TObject,\n source: Dictionary,\n options?: MixinOptions\n ): TResult;\n\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): TResult;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): LoDashImplicitObjectWrapper;\n\n /**\n * @see _.mixin\n */\n mixin(\n options?: MixinOptions\n ): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.mixin\n */\n mixin(\n source: Dictionary,\n options?: MixinOptions\n ): LoDashExplicitObjectWrapper;\n\n /**\n * @see _.mixin\n */\n mixin(\n options?: MixinOptions\n ): LoDashExplicitObjectWrapper;\n }\n\n //_.noConflict\n interface LoDashStatic {\n /**\n * Reverts the _ variable to its previous value and returns a reference to the lodash function.\n *\n * @return Returns the lodash function.\n */\n noConflict(): typeof _;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.noConflict\n */\n noConflict(): typeof _;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.noConflict\n */\n noConflict(): LoDashExplicitObjectWrapper;\n }\n\n //_.noop\n interface LoDashStatic {\n /**\n * A no-operation function that returns undefined regardless of the arguments it receives.\n *\n * @return undefined\n */\n noop(...args: any[]): void;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.noop\n */\n noop(...args: any[]): void;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.noop\n */\n noop(...args: any[]): _.LoDashExplicitWrapper;\n }\n\n //_.nthArg\n interface LoDashStatic {\n /**\n * Creates a function that returns its nth argument.\n *\n * @param n The index of the argument to return.\n * @return Returns the new function.\n */\n nthArg(n?: number): TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.nthArg\n */\n nthArg(): LoDashImplicitObjectWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.nthArg\n */\n nthArg(): LoDashExplicitObjectWrapper;\n }\n\n //_.over\n interface LoDashStatic {\n /**\n * Creates a function that invokes iteratees with the arguments provided to the created function and returns\n * their results.\n *\n * @param iteratees The iteratees to invoke.\n * @return Returns the new function.\n */\n over(...iteratees: Array>): (...args: any[]) => TResult[];\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.over\n */\n over(...iteratees: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => TResult[]>;\n }\n\n //_.overEvery\n interface LoDashStatic {\n /**\n * Creates a function that checks if all of the predicates return truthy when invoked with the arguments\n * provided to the created function.\n *\n * @param predicates The predicates to check.\n * @return Returns the new function.\n */\n overEvery(...predicates: Array>): (...args: any[]) => boolean;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overEvery\n */\n overEvery(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n //_.overSome\n interface LoDashStatic {\n /**\n * Creates a function that checks if any of the predicates return truthy when invoked with the arguments\n * provided to the created function.\n *\n * @param predicates The predicates to check.\n * @return Returns the new function.\n */\n overSome(...predicates: Array>): (...args: any[]) => boolean;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashImplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.overSome\n */\n overSome(...predicates: Array>): LoDashExplicitObjectWrapper<(...args: any[]) => boolean>;\n }\n\n //_.property\n interface LoDashStatic {\n /**\n * Creates a function that returns the property value at path on a given object.\n *\n * @param path The path of the property to get.\n * @return Returns the new function.\n */\n property(path: Many): (obj: TObj) => TResult;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.property\n */\n property(): LoDashImplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashImplicitArrayWrapper {\n /**\n * @see _.property\n */\n property(): LoDashImplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.property\n */\n property(): LoDashExplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n interface LoDashExplicitArrayWrapper {\n /**\n * @see _.property\n */\n property(): LoDashExplicitObjectWrapper<(obj: TObj) => TResult>;\n }\n\n //_.propertyOf\n interface LoDashStatic {\n /**\n * The opposite of _.property; this method creates a function that returns the property value at a given path\n * on object.\n *\n * @param object The object to query.\n * @return Returns the new function.\n */\n propertyOf(object: T): (path: Many) => any;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.propertyOf\n */\n propertyOf(): LoDashImplicitObjectWrapper<(path: Many) => any>;\n }\n\n interface LoDashExplicitObjectWrapper {\n /**\n * @see _.propertyOf\n */\n propertyOf(): LoDashExplicitObjectWrapper<(path: Many) => any>;\n }\n\n //_.range\n interface LoDashStatic {\n /**\n * Creates an array of numbers (positive and/or negative) progressing from start up to, but not including, end.\n * If end is not specified it’s set to start with start then set to 0. If end is less than start a zero-length\n * range is created unless a negative step is specified.\n *\n * @param start The start of the range.\n * @param end The end of the range.\n * @param step The value to increment or decrement by.\n * @return Returns a new range array.\n */\n range(\n start: number,\n end: number,\n step?: number\n ): number[];\n\n /**\n * @see _.range\n */\n range(\n end: number,\n step?: number\n ): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.range\n */\n range(\n end?: number,\n step?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.range\n */\n range(\n end?: number,\n step?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.rangeRight\n interface LoDashStatic {\n /**\n * This method is like `_.range` except that it populates values in\n * descending order.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {number} [start=0] The start of the range.\n * @param {number} end The end of the range.\n * @param {number} [step=1] The value to increment or decrement by.\n * @returns {Array} Returns the new array of numbers.\n * @example\n *\n * _.rangeRight(4);\n * // => [3, 2, 1, 0]\n *\n * _.rangeRight(-4);\n * // => [-3, -2, -1, 0]\n *\n * _.rangeRight(1, 5);\n * // => [4, 3, 2, 1]\n *\n * _.rangeRight(0, 20, 5);\n * // => [15, 10, 5, 0]\n *\n * _.rangeRight(0, -4, -1);\n * // => [-3, -2, -1, 0]\n *\n * _.rangeRight(1, 4, 0);\n * // => [1, 1, 1]\n *\n * _.rangeRight(0);\n * // => []\n */\n rangeRight(\n start: number,\n end: number,\n step?: number\n ): number[];\n\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end: number,\n step?: number\n ): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end?: number,\n step?: number\n ): LoDashImplicitArrayWrapper;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.rangeRight\n */\n rangeRight(\n end?: number,\n step?: number\n ): LoDashExplicitArrayWrapper;\n }\n\n //_.runInContext\n interface LoDashStatic {\n /**\n * Create a new pristine lodash function using the given context object.\n *\n * @param context The context object.\n * @return Returns a new lodash function.\n */\n runInContext(context?: Object): typeof _;\n }\n\n interface LoDashImplicitObjectWrapper {\n /**\n * @see _.runInContext\n */\n runInContext(): typeof _;\n }\n\n // _.stubArray\n interface LoDashStatic {\n /**\n * This method returns a new empty array.\n *\n * @returns Returns the new empty array.\n */\n stubArray(): any[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubArray\n */\n stubArray(): any[];\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubArray\n */\n stubArray(): _.LoDashExplicitArrayWrapper;\n }\n\n // _.stubFalse\n interface LoDashStatic {\n /**\n * This method returns `false`.\n *\n * @returns Returns `false`.\n */\n stubFalse(): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubFalse\n */\n stubFalse(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubFalse\n */\n stubFalse(): _.LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns a new empty object.\n *\n * @returns Returns the new empty object.\n */\n stubObject(): Object;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubObject\n */\n stubObject(): Object;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubObject\n */\n stubObject(): _.LoDashExplicitObjectWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns an empty string.\n *\n * @returns Returns the empty string.\n */\n stubString(): string;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubString\n */\n stubString(): string;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubString\n */\n stubString(): _.LoDashExplicitWrapper;\n }\n\n interface LoDashStatic {\n /**\n * This method returns `true`.\n *\n * @returns Returns `true`.\n */\n stubTrue(): boolean;\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.stubTrue\n */\n stubTrue(): boolean;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.stubTrue\n */\n stubTrue(): _.LoDashExplicitWrapper;\n }\n\n //_.times\n interface LoDashStatic {\n /**\n * Invokes the iteratee function n times, returning an array of the results of each invocation. The iteratee\n * is invoked with one argument; (index).\n *\n * @param n The number of times to invoke iteratee.\n * @param iteratee The function invoked per iteration.\n * @return Returns the array of results.\n */\n times(\n n: number,\n iteratee: (num: number) => TResult\n ): TResult[];\n\n /**\n * @see _.times\n */\n times(n: number): number[];\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.times\n */\n times(\n iteratee: (num: number) => TResult\n ): TResult[];\n\n /**\n * @see _.times\n */\n times(): number[];\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.times\n */\n times(\n iteratee: (num: number) => TResult\n ): LoDashExplicitArrayWrapper;\n\n /**\n * @see _.times\n */\n times(): LoDashExplicitArrayWrapper;\n }\n\n //_.toPath\n interface LoDashStatic {\n /**\n * Converts `value` to a property path array.\n *\n * @static\n * @memberOf _\n * @category Util\n * @param {*} value The value to convert.\n * @returns {Array} Returns the new property path array.\n * @example\n *\n * _.toPath('a.b.c');\n * // => ['a', 'b', 'c']\n *\n * _.toPath('a[0].b.c');\n * // => ['a', '0', 'b', 'c']\n *\n * var path = ['a', 'b', 'c'],\n * newPath = _.toPath(path);\n *\n * console.log(newPath);\n * // => ['a', 'b', 'c']\n *\n * console.log(path === newPath);\n * // => false\n */\n toPath(value: any): string[];\n }\n\n interface LoDashImplicitWrapperBase {\n /**\n * @see _.toPath\n */\n toPath(): LoDashImplicitWrapper;\n }\n\n interface LoDashExplicitWrapperBase {\n /**\n * @see _.toPath\n */\n toPath(): LoDashExplicitWrapper;\n }\n\n //_.uniqueId\n interface LoDashStatic {\n /**\n * Generates a unique ID. If prefix is provided the ID is appended to it.\n *\n * @param prefix The value to prefix the ID with.\n * @return Returns the unique ID.\n */\n uniqueId(prefix?: string): string;\n }\n\n interface LoDashImplicitWrapper {\n /**\n * @see _.uniqueId\n */\n uniqueId(): string;\n }\n\n interface LoDashExplicitWrapper {\n /**\n * @see _.uniqueId\n */\n uniqueId(): LoDashExplicitWrapper;\n }\n\n type ListIterator = (value: T, index: number, collection: List) => TResult;\n\n type ListIteratorTypeGuard = (value: T, index: number, collection: List) => value is S;\n\n type DictionaryIterator = (value: T, key: string, collection: Dictionary) => TResult;\n\n type DictionaryIteratorTypeGuard = (value: T, key: string, collection: Dictionary) => value is S;\n\n type NumericDictionaryIterator = (value: T, key: number, collection: Dictionary) => TResult;\n\n type ObjectIterator = (element: T, key: string, collection: any) => TResult;\n\n type StringIterator = (char: string, index: number, string: string) => TResult;\n\n type MemoVoidIterator = (prev: TResult, curr: T, indexOrKey: any, list: T[]) => void;\n\n type MemoIterator = (prev: TResult, curr: T, indexOrKey: any, list: T[]) => TResult;\n\n type MemoVoidArrayIterator = (acc: TResult, curr: T, index: number, arr: T[]) => void;\n type MemoVoidDictionaryIterator = (acc: TResult, curr: T, key: string, dict: Dictionary) => void;\n\n /** Common interface between Arrays and jQuery objects */\n type List = ArrayLike;\n\n interface Dictionary {\n [index: string]: T;\n }\n\n interface NumericDictionary {\n [index: number]: T;\n }\n\n interface StringRepresentable {\n toString(): string;\n }\n\n interface Cancelable {\n cancel(): void;\n flush(): void;\n }\n}\n\n// Backward compatibility with --target es5\ndeclare global {\n interface Set { }\n interface Map { }\n interface WeakSet { }\n interface WeakMap { }\n}\n" } + { "path": "/a/b/node_modules/.staging/@types/lodash-e56c4fe7/index.d.ts", "content": "\n// Stub for lodash\nexport = _;\nexport as namespace _;\ndeclare var _: _.LoDashStatic;\ndeclare namespace _ {\n interface LoDashStatic {\n someProp: string;\n }\n class SomeClass {\n someMethod(): void;\n }\n}" } ); verifyAfterPartialOrCompleteNpmInstall(2); From 680994ea42e8c5f43ca0bc2ff237966d8c0a85fd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Sep 2017 14:43:34 -0700 Subject: [PATCH 095/144] Better log for update graph and delay operations --- src/compiler/utilities.ts | 3 +++ src/server/editorServices.ts | 2 +- src/server/project.ts | 7 ++++--- src/server/server.ts | 2 +- src/server/utilities.ts | 18 +++++++++++++----- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e17e0e96997..5785692017d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3592,7 +3592,10 @@ namespace ts { const watcher = addWatch(host, file, (fileName, cbOptional1?) => { const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : ""; log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`); + const start = timestamp(); cb(fileName, cbOptional1, optional); + const elapsed = timestamp() - start; + log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`); }, optional); return { close: () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3114bb97e82..5c160ef007c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -401,7 +401,7 @@ namespace ts.server { this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); - this.throttledOperations = new ThrottledOperations(this.host); + this.throttledOperations = new ThrottledOperations(this.host, this.logger); this.typingsInstaller.attach(this); diff --git a/src/server/project.ts b/src/server/project.ts index afeb533550b..b8c16138e5e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -789,7 +789,8 @@ namespace ts.server { private updateGraphWorker() { const oldProgram = this.program; - this.writeLog(`Starting Update graph worker: Project: ${this.getProjectName()}`); + this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`); + const start = timestamp(); this.resolutionCache.startCachingPerDirectoryResolution(); this.program = this.languageService.getProgram(); this.resolutionCache.finishCachingPerDirectoryResolution(); @@ -843,8 +844,8 @@ namespace ts.server { scriptInfoToDetach.detachFromProject(this); } }); - - this.writeLog(`Finishing Update graph worker: Project: ${this.getProjectName()}`); + const elapsed = timestamp() - start; + this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`); return hasChanges; } diff --git a/src/server/server.ts b/src/server/server.ts index 0ccff7c4d25..c999c765447 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -252,7 +252,7 @@ namespace ts.server { readonly typingSafeListLocation: string, private readonly npmLocation: string | undefined, private newLine: string) { - this.throttledOperations = new ThrottledOperations(host); + this.throttledOperations = new ThrottledOperations(host, this.logger); if (eventPort) { const s = net.connect({ port: eventPort }, () => { this.socket = s; diff --git a/src/server/utilities.ts b/src/server/utilities.ts index c596176ad86..4b9af6481db 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -173,10 +173,15 @@ namespace ts.server { export function createSortedArray(): SortedArray { return [] as SortedArray; } +} +/* @internal */ +namespace ts.server { export class ThrottledOperations { - private pendingTimeouts: Map = createMap(); - constructor(private readonly host: ServerHost) { + private readonly pendingTimeouts: Map = createMap(); + private readonly logger?: Logger | undefined; + constructor(private readonly host: ServerHost, logger: Logger) { + this.logger = logger.hasLevel(LogLevel.verbose) && logger; } public schedule(operationId: string, delay: number, cb: () => void) { @@ -187,10 +192,16 @@ namespace ts.server { } // schedule new operation, pass arguments this.pendingTimeouts.set(operationId, this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb)); + if (this.logger) { + this.logger.info(`Scheduled: ${operationId}${pendingTimeout ? ", Cancelled earlier one" : ""}`); + } } private static run(self: ThrottledOperations, operationId: string, cb: () => void) { self.pendingTimeouts.delete(operationId); + if (self.logger) { + self.logger.info(`Running: ${operationId}`); + } cb(); } } @@ -221,10 +232,7 @@ namespace ts.server { } } } -} -/* @internal */ -namespace ts.server { export function getBaseConfigFileName(configFilePath: NormalizedPath): "tsconfig.json" | "jsconfig.json" | undefined { const base = getBaseFileName(configFilePath); return base === "tsconfig.json" || base === "jsconfig.json" ? base : undefined; From c8e711c3a37b0bdd0cd097a50fdcd8bee6c501e1 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Sep 2017 19:54:51 -0700 Subject: [PATCH 096/144] Invalidate resolution of the failed lookup only if its one of the default extension or is one of the failed lookup location without that default extension --- src/compiler/resolutionCache.ts | 180 ++++++++++++++++++++++---------- src/compiler/types.ts | 5 +- src/server/session.ts | 18 ++-- 3 files changed, 139 insertions(+), 64 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 56dd48aaebf..3a989eaa138 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -30,6 +30,10 @@ namespace ts { isInvalidated?: boolean; } + interface ResolutionWithResolvedFileName { + resolvedFileName: string | undefined; + } + export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; getCompilationSettings(): CompilerOptions; @@ -59,10 +63,15 @@ namespace ts { // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); const perDirectoryResolvedModuleNames = createMap>(); + const resolvedTypeReferenceDirectives = createMap>(); const perDirectoryResolvedTypeReferenceDirectives = createMap>(); + const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; + const customFailedLookupPaths = createMap(); + const directoryWatchesOfFailedLookups = createMap(); let rootDir: string; let rootPath: Path; @@ -85,6 +94,14 @@ namespace ts { clear }; + function getResolvedModule(resolution: ResolvedModuleWithFailedLookupLocations) { + return resolution.resolvedModule; + } + + function getResolvedTypeReferenceDirective(resolution: ResolvedTypeReferenceDirectiveWithFailedLookupLocations) { + return resolution.resolvedTypeReferenceDirective; + } + function setRootDirectory(dir: string) { Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size); rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory())); @@ -100,6 +117,7 @@ namespace ts { function clear() { clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); + customFailedLookupPaths.clear(); closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); @@ -160,14 +178,13 @@ namespace ts { return primaryResult; } - function resolveNamesWithLocalCache( + function resolveNamesWithLocalCache( names: string[], containingFile: string, cache: Map>, perDirectoryCache: Map>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, - getResult: (s: T) => R, - getResultFileName: (result: R) => string | undefined, + getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R, logChanges: boolean): R[] { const path = resolutionHost.toPath(containingFile); @@ -208,7 +225,7 @@ namespace ts { } Debug.assert(resolution !== undefined && !resolution.isInvalidated); seenNamesInFile.set(name, true); - resolvedModules.push(getResult(resolution)); + resolvedModules.push(getResolutionFromNameResolutionWithFailedLookupLocations(resolution)); } // Stop watching and remove the unused name @@ -228,15 +245,15 @@ namespace ts { if (!oldResolution || !newResolution || oldResolution.isInvalidated) { return false; } - const oldResult = getResult(oldResolution); - const newResult = getResult(newResolution); + const oldResult = getResolutionFromNameResolutionWithFailedLookupLocations(oldResolution); + const newResult = getResolutionFromNameResolutionWithFailedLookupLocations(newResolution); if (oldResult === newResult) { return true; } if (!oldResult || !newResult) { return false; } - return getResultFileName(oldResult) === getResultFileName(newResult); + return oldResult.resolvedFileName === newResult.resolvedFileName; } } @@ -244,7 +261,7 @@ namespace ts { return resolveNamesWithLocalCache( typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, - resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName, + resolveTypeReferenceDirective, getResolvedTypeReferenceDirective, /*logChanges*/ false ); } @@ -253,7 +270,7 @@ namespace ts { return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, - resolveModuleName, m => m.resolvedModule, r => r.resolvedFileName, + resolveModuleName, getResolvedModule, logChanges ); } @@ -300,13 +317,24 @@ namespace ts { return { dir, dirPath }; } + function isPathWithDefaultFailedLookupExtension(path: Path) { + return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); + } + function watchFailedLookupLocationOfResolution( resolution: T, startIndex?: number ) { if (resolution && resolution.failedLookupLocations) { for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) { const failedLookupLocation = resolution.failedLookupLocations[i]; - const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); if (dirWatcher) { dirWatcher.refCount++; @@ -330,7 +358,17 @@ namespace ts { if (resolution && resolution.failedLookupLocations) { for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) { const failedLookupLocation = resolution.failedLookupLocations[i]; - const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, resolutionHost.toPath(failedLookupLocation)); + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); + } + else { + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); + } + } + const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); // Do not close the watcher yet since it might be needed by other failed lookup locations. dirWatcher.refCount--; @@ -349,72 +387,106 @@ namespace ts { // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { - const isChangedFailedLookupLocation: (location: string) => boolean = dirPath === fileOrFolderPath ? - // If the file watched directory is created/deleted invalidate any resolution has failed lookup in this directory - location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)) : - // Otherwise only the resolutions referencing the file or folder added - location => resolutionHost.toPath(location) === fileOrFolderPath; - if (invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation)) { + let isChangedFailedLookupLocation: (location: string) => boolean; + if (dirPath === fileOrFolderPath) { + // Watching directory is created + // Invalidate any resolution has failed lookup in this directory + isChangedFailedLookupLocation = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)); + } + else { + // Some file or folder in the watching directory is created + // Return early if it does not have any of the watching extension or not the custom failed lookup path + if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) { + return; + } + // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created + isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath; + } + const hasChangedFailedLookupLocation = (resolution: NameResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); + if (invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation)) { resolutionHost.onInvalidatedResolution(); } } }, WatchDirectoryFlags.Recursive); } - function invalidateResolutionCacheOfDeletedFile( - deletedFilePath: Path, + function invalidateResolutionCache( cache: Map>, - getResult: (s: T) => R, - getResultFileName: (result: R) => string | undefined) { - cache.forEach((value, path) => { - if (path === deletedFilePath) { - cache.delete(path); - if (value) { - value.forEach(stopWatchFailedLookupLocationOfResolution); + ignoreFile: (resolutions: Map, containingFilePath: Path) => boolean, + isInvalidatedResolution: (resolution: T) => boolean + ) { + const seen = createMap>(); + cache.forEach((resolutions, containingFilePath) => { + if (!ignoreFile(resolutions, containingFilePath as Path) && resolutions) { + const dirPath = getDirectoryPath(containingFilePath); + let seenInDir = seen.get(dirPath); + if (!seenInDir) { + seenInDir = createMap(); + seen.set(dirPath, seenInDir); } - } - else if (value) { - value.forEach(resolution => { - if (resolution && !resolution.isInvalidated) { - const result = getResult(resolution); - if (result) { - if (resolutionHost.toPath(getResultFileName(result)) === deletedFilePath) { - resolution.isInvalidated = true; - (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); - } - } + resolutions.forEach((resolution, name) => { + if (seenInDir.has(name)) { + return; + } + seenInDir.set(name, true); + if (!resolution.isInvalidated && isInvalidatedResolution(resolution)) { + // Mark the file as needing re-evaluation of module resolution instead of using it blindly. + resolution.isInvalidated = true; + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFilePath, true); } }); } }); } + function invalidateResolutionCacheOfDeletedFile( + deletedFilePath: Path, + cache: Map>, + getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R, + ) { + invalidateResolutionCache( + cache, + // Ignore file thats same as deleted file path, and handle it here + (resolutions, containingFilePath) => { + if (containingFilePath !== deletedFilePath) { + return false; + } + + // Deleted file, stop watching failed lookups for all the resolutions in the file + cache.delete(containingFilePath); + resolutions.forEach(stopWatchFailedLookupLocationOfResolution); + return true; + }, + // Resolution is invalidated if the resulting file name is same as the deleted file path + resolution => { + const result = getResolutionFromNameResolutionWithFailedLookupLocations(resolution); + return result && resolutionHost.toPath(result.resolvedFileName) === deletedFilePath; + } + ); + } + function invalidateResolutionOfFile(filePath: Path) { - invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, m => m.resolvedModule, r => r.resolvedFileName); - invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, m => m.resolvedTypeReferenceDirective, r => r.resolvedFileName); + invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, getResolvedModule); + invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, getResolvedTypeReferenceDirective); } function invalidateResolutionCacheOfFailedLookupLocation( cache: Map>, - isChangedFailedLookupLocation: (location: string) => boolean + hasChangedFailedLookupLocation: (resolution: T) => boolean ) { - cache.forEach((value, containingFile) => { - if (value) { - value.forEach(resolution => { - if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, isChangedFailedLookupLocation)) { - // Mark the file as needing re-evaluation of module resolution instead of using it blindly. - resolution.isInvalidated = true; - (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); - } - }); - } - }); + invalidateResolutionCache( + cache, + // Do not ignore any file + returnFalse, + // Resolution is invalidated if the resulting file name is same as the deleted file path + hasChangedFailedLookupLocation + ); } - function invalidateResolutionOfFailedLookupLocation(isChangedFailedLookupLocation: (location: string) => boolean) { + function invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation: (resolution: NameResolutionWithFailedLookupLocations) => boolean) { const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; - invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, isChangedFailedLookupLocation); - invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, isChangedFailedLookupLocation); + invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, hasChangedFailedLookupLocation); + invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, hasChangedFailedLookupLocation); return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a105e5fcbfe..d033de6a65c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4010,7 +4010,8 @@ namespace ts { Tsx = ".tsx", Dts = ".d.ts", Js = ".js", - Jsx = ".jsx" + Jsx = ".jsx", + Json = ".json" } export interface ResolvedModuleWithFailedLookupLocations { @@ -4025,7 +4026,7 @@ namespace ts { // True if the type declaration file was found in a primary lookup location primary: boolean; // The location of the .d.ts file we located, or undefined if resolution failed - resolvedFileName?: string; + resolvedFileName: string | undefined; } export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { diff --git a/src/server/session.ts b/src/server/session.ts index 92f4b34a46e..6bcfef554d3 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -469,12 +469,14 @@ namespace ts.server { index++; if (checkSpec.project.containsFile(checkSpec.fileName, requireOpen)) { this.syntacticCheck(checkSpec.fileName, checkSpec.project); - next.immediate(() => { - this.semanticCheck(checkSpec.fileName, checkSpec.project); - if (checkList.length > index) { - next.delay(followMs, checkOne); - } - }); + if (this.changeSeq === seq) { + next.immediate(() => { + this.semanticCheck(checkSpec.fileName, checkSpec.project); + if (checkList.length > index) { + next.delay(followMs, checkOne); + } + }); + } } } }; @@ -1287,11 +1289,11 @@ namespace ts.server { const start = scriptInfo.lineOffsetToPosition(args.line, args.offset); const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset); if (start >= 0) { + this.changeSeq++; this.projectService.applyChangesToFile(scriptInfo, [{ span: { start, length: end - start }, newText: args.insertString }]); - this.changeSeq++; } } @@ -1698,8 +1700,8 @@ namespace ts.server { return this.requiredResponse(converted); }, [CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => { - this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles); this.changeSeq++; + this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles, request.arguments.closedFiles); // TODO: report errors return this.requiredResponse(/*response*/ true); }, From 29e93c38a5a0e81c1bffe57bad4529cef36fbcac Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Sep 2017 09:12:36 -0700 Subject: [PATCH 097/144] Update the test cases for project changed event since it doesnt apply to the edits to open files --- .../unittests/tsserverProjectSystem.ts | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 3537fde9a49..55d0e26ad1d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4953,18 +4953,23 @@ namespace ts.projectSystem { } function verifyProjectChangedEvent(filesToEmit: FileOrFolder[], filesToReload?: FileOrFolder[], additionalChangedFiles?: FileOrFolder[]) { - const changedFiles = mapDefined(additionalChangedFiles ? filesToEmit.concat(additionalChangedFiles) : filesToEmit, f => f !== configFile ? f.path : undefined); host.reloadFS(filesToReload || files); host.runQueuedTimeoutCallbacks(); - const project = projectService.configuredProjects.get(configFile.path); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, - data: { - project, - changedFiles, - filesToEmit: mapDefined(filesToEmit, f => f !== libFile && f !== configFile ? f.path : undefined) - } - }]); + if (filesToEmit.length) { + const project = projectService.configuredProjects.get(configFile.path); + const changedFiles = mapDefined(additionalChangedFiles ? filesToEmit.concat(additionalChangedFiles) : filesToEmit, f => f !== configFile ? f.path : undefined); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles, + filesToEmit: mapDefined(filesToEmit, f => f !== libFile && f !== configFile ? f.path : undefined) + } + }]); + } + else { + verifyProjectChangedEventHandler([]); + } } function updateContentOfOpenFile(file: FileOrFolder, newContent: string) { @@ -5000,15 +5005,15 @@ namespace ts.projectSystem { // Change file1Consumer1 content to `export let y = Foo();` updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); - verifyProjectChangedEvent([file1Consumer1]); + verifyProjectChangedEvent([]); // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer2]); + verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); // Add the import statements back to file1Consumer1 updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); - verifyProjectChangedEvent([file1Consumer1]); + verifyProjectChangedEvent([]); // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; @@ -5095,11 +5100,11 @@ namespace ts.projectSystem { }); updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); - verifyProjectChangedEvent([file1Consumer1, file1Consumer1Consumer1]); + verifyProjectChangedEvent([]); // Doesnt change the shape of file1Consumer1 moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); // Change both files before the timeout updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); @@ -5120,12 +5125,12 @@ namespace ts.projectSystem { /// export var t2 = 10;` }; - const { configFile, verifyProjectChangedEvent, updateContentOfOpenFile } = getInitialState({ + const { configFile, verifyProjectChangedEvent } = getInitialState({ getAdditionalFileOrFolder: () => [file1, file2], firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] }); - updateContentOfOpenFile(file1, file1.content + "export var t3 = 10;"); + file2.content += "export var t3 = 10;"; verifyProjectChangedEvent([file1, file2], [file1, file2, libFile, configFile]); }); @@ -5157,7 +5162,7 @@ namespace ts.projectSystem { }); updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); - verifyProjectChangedEvent([referenceFile1], [libFile, referenceFile1, configFile]); + verifyProjectChangedEvent([], [libFile, referenceFile1, configFile]); // Create module File2 and see both files are saved verifyProjectChangedEvent([referenceFile1, moduleFile2], [libFile, moduleFile2, referenceFile1, configFile]); From b179cd1e1cc74efb6d6202ca0d70c19adc423713 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Sep 2017 14:48:16 -0700 Subject: [PATCH 098/144] Return configured project being closed by config file --- src/server/editorServices.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 5c160ef007c..4e9ef31ba09 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1979,11 +1979,12 @@ namespace ts.server { } } - private closeConfiguredProject(configFile: NormalizedPath): void { + private closeConfiguredProject(configFile: NormalizedPath) { const configuredProject = this.findConfiguredProjectByProjectName(configFile); if (configuredProject && configuredProject.deleteOpenRef() === 0) { this.removeProject(configuredProject); } + return configuredProject; } closeExternalProject(uncheckedFileName: string, suppressRefresh = false): void { From 67f9533c67a429d480d66d16be3a278b3e5604fa Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Sep 2017 17:27:00 -0700 Subject: [PATCH 099/144] Limit the resolution invalidation to max number of files as invalidation for larger cache might take more time than to just recalculate resolutions --- src/compiler/resolutionCache.ts | 199 +++++++++--------- src/compiler/watchedProgram.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 79 +++++++ src/server/editorServices.ts | 12 +- src/server/project.ts | 31 +-- src/server/scriptInfo.ts | 2 +- 6 files changed, 212 insertions(+), 113 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 3a989eaa138..54122440024 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -12,6 +12,7 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; + removeResolutionsOfFile(filePath: Path): void; createHasInvalidatedResolution(): HasInvalidatedResolution; startCachingPerDirectoryResolution(): void; @@ -25,7 +26,7 @@ namespace ts { clear(): void; } - interface NameResolutionWithFailedLookupLocations { + interface ResolutionWithFailedLookupLocations { readonly failedLookupLocations: ReadonlyArray; isInvalidated?: boolean; } @@ -45,6 +46,7 @@ namespace ts { projectName?: string; getGlobalCache?(): string | undefined; writeLog(s: string): void; + maxNumberOfFilesToIterateForInvalidation?: number; } interface DirectoryWatchesOfFailedLookup { @@ -54,9 +56,17 @@ namespace ts { refCount: number; } + /*@internal*/ + export const maxNumberOfFilesToIterateForInvalidation = 256; + + interface GetResolutionWithResolvedFileName { + (resolution: T): R; + } + export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; + let allFilesHaveInvalidatedResolution = false; // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. // The key in the map is source file's path. @@ -86,6 +96,7 @@ namespace ts { finishCachingPerDirectoryResolution, resolveModuleNames, resolveTypeReferenceDirectives, + removeResolutionsOfFile, invalidateResolutionOfFile, createHasInvalidatedResolution, setRootDirectory, @@ -121,6 +132,7 @@ namespace ts { closeTypeRootsWatch(); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); + allFilesHaveInvalidatedResolution = false; Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0); } @@ -135,6 +147,11 @@ namespace ts { } function createHasInvalidatedResolution(): HasInvalidatedResolution { + if (allFilesHaveInvalidatedResolution) { + // Any file asked would have invalidated resolution + filesWithInvalidatedResolutions = undefined; + return returnTrue; + } const collected = filesWithInvalidatedResolutions; filesWithInvalidatedResolutions = undefined; return path => collected && collected.has(path); @@ -145,6 +162,7 @@ namespace ts { } function finishCachingPerDirectoryResolution() { + allFilesHaveInvalidatedResolution = false; directoryWatchesOfFailedLookups.forEach((watcher, path) => { if (watcher.refCount === 0) { directoryWatchesOfFailedLookups.delete(path); @@ -178,13 +196,13 @@ namespace ts { return primaryResult; } - function resolveNamesWithLocalCache( + function resolveNamesWithLocalCache( names: string[], containingFile: string, cache: Map>, perDirectoryCache: Map>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, - getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, logChanges: boolean): R[] { const path = resolutionHost.toPath(containingFile); @@ -203,7 +221,7 @@ namespace ts { for (const name of names) { let resolution = resolutionsInFile.get(name); // Resolution is valid if it is present and not invalidated - if (!resolution || resolution.isInvalidated) { + if (allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated) { const existingResolution = resolution; const resolutionInDirectory = perDirectoryResolution.get(name); if (resolutionInDirectory) { @@ -225,7 +243,7 @@ namespace ts { } Debug.assert(resolution !== undefined && !resolution.isInvalidated); seenNamesInFile.set(name, true); - resolvedModules.push(getResolutionFromNameResolutionWithFailedLookupLocations(resolution)); + resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } // Stop watching and remove the unused name @@ -245,8 +263,8 @@ namespace ts { if (!oldResolution || !newResolution || oldResolution.isInvalidated) { return false; } - const oldResult = getResolutionFromNameResolutionWithFailedLookupLocations(oldResolution); - const newResult = getResolutionFromNameResolutionWithFailedLookupLocations(newResolution); + const oldResult = getResolutionWithResolvedFileName(oldResolution); + const newResult = getResolutionWithResolvedFileName(newResolution); if (oldResult === newResult) { return true; } @@ -321,9 +339,7 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchFailedLookupLocationOfResolution( - resolution: T, startIndex?: number - ) { + function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations, startIndex?: number) { if (resolution && resolution.failedLookupLocations) { for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) { const failedLookupLocation = resolution.failedLookupLocations[i]; @@ -346,15 +362,11 @@ namespace ts { } } - function stopWatchFailedLookupLocationOfResolution( - resolution: T - ) { + function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); } - function stopWatchFailedLookupLocationOfResolutionFrom( - resolution: T, startIndex: number - ) { + function stopWatchFailedLookupLocationOfResolutionFrom(resolution: ResolutionWithFailedLookupLocations, startIndex: number) { if (resolution && resolution.failedLookupLocations) { for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) { const failedLookupLocation = resolution.failedLookupLocations[i]; @@ -387,107 +399,106 @@ namespace ts { // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { - let isChangedFailedLookupLocation: (location: string) => boolean; - if (dirPath === fileOrFolderPath) { - // Watching directory is created - // Invalidate any resolution has failed lookup in this directory - isChangedFailedLookupLocation = location => isInDirectoryPath(dirPath, resolutionHost.toPath(location)); - } - else { - // Some file or folder in the watching directory is created - // Return early if it does not have any of the watching extension or not the custom failed lookup path - if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) { - return; - } - // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created - isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath; - } - const hasChangedFailedLookupLocation = (resolution: NameResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); - if (invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation)) { + if (invalidateResolutionOfFailedLookupLocation(fileOrFolderPath, dirPath === fileOrFolderPath)) { resolutionHost.onInvalidatedResolution(); } } }, WatchDirectoryFlags.Recursive); } - function invalidateResolutionCache( + function removeResolutionsOfFileFromCache(cache: Map>, filePath: Path) { + // Deleted file, stop watching failed lookups for all the resolutions in the file + const resolutions = cache.get(filePath); + if (resolutions) { + resolutions.forEach(stopWatchFailedLookupLocationOfResolution); + cache.delete(filePath); + } + } + + function removeResolutionsOfFile(filePath: Path) { + removeResolutionsOfFileFromCache(resolvedModuleNames, filePath); + removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath); + } + + function invalidateResolutionCache( cache: Map>, - ignoreFile: (resolutions: Map, containingFilePath: Path) => boolean, - isInvalidatedResolution: (resolution: T) => boolean + isInvalidatedResolution: (resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean, + getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName ) { const seen = createMap>(); cache.forEach((resolutions, containingFilePath) => { - if (!ignoreFile(resolutions, containingFilePath as Path) && resolutions) { - const dirPath = getDirectoryPath(containingFilePath); - let seenInDir = seen.get(dirPath); - if (!seenInDir) { - seenInDir = createMap(); - seen.set(dirPath, seenInDir); - } - resolutions.forEach((resolution, name) => { - if (seenInDir.has(name)) { - return; - } - seenInDir.set(name, true); - if (!resolution.isInvalidated && isInvalidatedResolution(resolution)) { - // Mark the file as needing re-evaluation of module resolution instead of using it blindly. - resolution.isInvalidated = true; - (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFilePath, true); - } - }); + const dirPath = getDirectoryPath(containingFilePath); + let seenInDir = seen.get(dirPath); + if (!seenInDir) { + seenInDir = createMap(); + seen.set(dirPath, seenInDir); } + resolutions.forEach((resolution, name) => { + if (seenInDir.has(name)) { + return; + } + seenInDir.set(name, true); + if (!resolution.isInvalidated && isInvalidatedResolution(resolution, getResolutionWithResolvedFileName)) { + // Mark the file as needing re-evaluation of module resolution instead of using it blindly. + resolution.isInvalidated = true; + (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFilePath, true); + } + }); }); } - function invalidateResolutionCacheOfDeletedFile( - deletedFilePath: Path, - cache: Map>, - getResolutionFromNameResolutionWithFailedLookupLocations: (s: T) => R, - ) { - invalidateResolutionCache( - cache, - // Ignore file thats same as deleted file path, and handle it here - (resolutions, containingFilePath) => { - if (containingFilePath !== deletedFilePath) { - return false; - } + function hasReachedResolutionIterationLimit() { + const maxSize = resolutionHost.maxNumberOfFilesToIterateForInvalidation || maxNumberOfFilesToIterateForInvalidation; + return resolvedModuleNames.size > maxSize || resolvedTypeReferenceDirectives.size > maxSize; + } - // Deleted file, stop watching failed lookups for all the resolutions in the file - cache.delete(containingFilePath); - resolutions.forEach(stopWatchFailedLookupLocationOfResolution); - return true; - }, + function invalidateResolutions( + isInvalidatedResolution: (resolution: ResolutionWithFailedLookupLocations, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName) => boolean, + ) { + // If more than maxNumberOfFilesToIterateForInvalidation present, + // just invalidated all files and recalculate the resolutions for files instead + if (hasReachedResolutionIterationLimit()) { + allFilesHaveInvalidatedResolution = true; + return; + } + invalidateResolutionCache(resolvedModuleNames, isInvalidatedResolution, getResolvedModule); + invalidateResolutionCache(resolvedTypeReferenceDirectives, isInvalidatedResolution, getResolvedTypeReferenceDirective); + } + + function invalidateResolutionOfFile(filePath: Path) { + removeResolutionsOfFile(filePath); + invalidateResolutions( // Resolution is invalidated if the resulting file name is same as the deleted file path - resolution => { - const result = getResolutionFromNameResolutionWithFailedLookupLocations(resolution); - return result && resolutionHost.toPath(result.resolvedFileName) === deletedFilePath; + (resolution, getResolutionWithResolvedFileName) => { + const result = getResolutionWithResolvedFileName(resolution); + return result && resolutionHost.toPath(result.resolvedFileName) === filePath; } ); } - function invalidateResolutionOfFile(filePath: Path) { - invalidateResolutionCacheOfDeletedFile(filePath, resolvedModuleNames, getResolvedModule); - invalidateResolutionCacheOfDeletedFile(filePath, resolvedTypeReferenceDirectives, getResolvedTypeReferenceDirective); - } - - function invalidateResolutionCacheOfFailedLookupLocation( - cache: Map>, - hasChangedFailedLookupLocation: (resolution: T) => boolean - ) { - invalidateResolutionCache( - cache, - // Do not ignore any file - returnFalse, + function invalidateResolutionOfFailedLookupLocation(fileOrFolderPath: Path, isCreatingWatchedDirectory: boolean) { + let isChangedFailedLookupLocation: (location: string) => boolean; + if (isCreatingWatchedDirectory) { + // Watching directory is created + // Invalidate any resolution has failed lookup in this directory + isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrFolderPath, resolutionHost.toPath(location)); + } + else { + // Some file or folder in the watching directory is created + // Return early if it does not have any of the watching extension or not the custom failed lookup path + if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) { + return false; + } + // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created + isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath; + } + const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); + const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; + invalidateResolutions( // Resolution is invalidated if the resulting file name is same as the deleted file path hasChangedFailedLookupLocation ); - } - - function invalidateResolutionOfFailedLookupLocation(hasChangedFailedLookupLocation: (resolution: NameResolutionWithFailedLookupLocations) => boolean) { - const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; - invalidateResolutionCacheOfFailedLookupLocation(resolvedModuleNames, hasChangedFailedLookupLocation); - invalidateResolutionCacheOfFailedLookupLocation(resolvedTypeReferenceDirectives, hasChangedFailedLookupLocation); - return filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; + return allFilesHaveInvalidatedResolution || filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size !== invalidatedFilesCount; } function closeTypeRootsWatch() { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 0dba848eb1c..b9961a385dd 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -501,7 +501,7 @@ namespace ts { } else if (hostSourceFileInfo.sourceFile === oldSourceFile) { sourceFilesCache.delete(oldSourceFile.path); - resolutionCache.invalidateResolutionOfFile(oldSourceFile.path); + resolutionCache.removeResolutionsOfFile(oldSourceFile.path); } } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 55d0e26ad1d..e9561c25ff9 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -5168,6 +5168,85 @@ namespace ts.projectSystem { verifyProjectChangedEvent([referenceFile1, moduleFile2], [libFile, moduleFile2, referenceFile1, configFile]); }); }); + + describe("resolution when resolution cache size", () => { + function verifyWithMaxCacheLimit(limitHit: boolean) { + const file1: FileOrFolder = { + path: "/a/b/project/file1.ts", + content: 'import a from "file2"' + }; + const file2: FileOrFolder = { + path: "/a/b/node_modules/file2.d.ts", + content: "export class a { }" + }; + const file3: FileOrFolder = { + path: "/a/b/project/file3.ts", + content: "export class c { }" + }; + const configFile: FileOrFolder = { + path: "/a/b/project/tsconfig.json", + content: JSON.stringify({ compilerOptions: { typeRoots: [] } }) + }; + + const projectFiles = [file1, file3, libFile, configFile]; + const watchedRecursiveDirectories = ["/a/b/project", "/a/b/node_modules", "/a/node_modules", "/node_modules"]; + const host = createServerHost(projectFiles); + const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); + const projectService = session.getProjectService(); + verifyInitialOpen(file1); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); + verifyProject(); + if (limitHit) { + (project as ResolutionCacheHost).maxNumberOfFilesToIterateForInvalidation = 1; + } + + file3.content += "export class d {}"; + host.reloadFS(projectFiles); + host.checkTimeoutQueueLengthAndRun(2); + + // Since this is first event + verifyProject(); + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles: [libFile.path, file1.path, file3.path], + filesToEmit: [file1.path, file3.path] + } + }]); + + projectFiles.push(file2); + host.reloadFS(projectFiles); + host.runQueuedTimeoutCallbacks(); + watchedRecursiveDirectories.length = 2; + verifyProject(); + + const changedFiles = limitHit ? [file1.path, file2.path, file3.path, libFile.path] : [file1.path, file2.path]; + verifyProjectChangedEventHandler([{ + eventName: server.ProjectChangedEvent, + data: { + project, + changedFiles, + filesToEmit: changedFiles + } + }]); + + function verifyProject() { + checkProjectActualFiles(project, map(projectFiles, file => file.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + } + } + + it("limit not hit", () => { + verifyWithMaxCacheLimit(/*limitHit*/ false); + }); + + it("limit hit", () => { + verifyWithMaxCacheLimit(/*limitHit*/ true); + }); + }); } describe("when event handler is set in the session", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4e9ef31ba09..ae9757d8a9b 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -876,6 +876,8 @@ namespace ts.server { unorderedRemoveItem(this.openFiles, info); + const fileExists = this.host.fileExists(info.fileName); + // collect all projects that should be removed let projectsToRemove: Project[]; for (const p of info.containingProjects) { @@ -896,7 +898,7 @@ namespace ts.server { (projectsToRemove || (projectsToRemove = [])).push(p); } else { - p.removeFile(info); + p.removeFile(info, fileExists, /*detachFromProject*/ true); } } @@ -926,7 +928,7 @@ namespace ts.server { // If the current info is being just closed - add the watcher file to track changes // But if file was deleted, handle that part - if (this.host.fileExists(info.fileName)) { + if (fileExists) { this.watchClosedScriptInfo(info); } else { @@ -1447,7 +1449,7 @@ namespace ts.server { path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName); const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { - project.removeFile(existingValue); + project.removeFile(existingValue, /*fileExists*/ false, /*detachFromProject*/ true); } projectRootFilesMap.set(path, normalizedPath); scriptInfo = normalizedPath; @@ -1476,7 +1478,7 @@ namespace ts.server { projectRootFilesMap.forEach((value, path) => { if (!newRootScriptInfoMap.has(path)) { if (isScriptInfo(value)) { - project.removeFile(value); + project.removeFile(value, project.fileExists(path), /*detachFromProject*/ true); } else { projectRootFilesMap.delete(path); @@ -1806,7 +1808,7 @@ namespace ts.server { this.removeProject(inferredProject); } else { - inferredProject.removeFile(info); + inferredProject.removeFile(info, /*fileExists*/ true, /*detachFromProject*/ true); } } } diff --git a/src/server/project.ts b/src/server/project.ts index b8c16138e5e..1a89fcb7704 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -667,11 +667,17 @@ namespace ts.server { this.markAsDirty(); } - removeFile(info: ScriptInfo, detachFromProject = true) { + removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean) { if (this.isRoot(info)) { this.removeRoot(info); } - this.resolutionCache.invalidateResolutionOfFile(info.path); + if (fileExists) { + // If file is present, just remove the resolutions for the file + this.resolutionCache.removeResolutionsOfFile(info.path); + } + else { + this.resolutionCache.invalidateResolutionOfFile(info.path); + } this.cachedUnresolvedImportsPerFile.remove(info.path); if (detachFromProject) { @@ -807,10 +813,7 @@ namespace ts.server { continue; } // new program does not contain this file - detach it from the project - const scriptInfoToDetach = this.projectService.getScriptInfo(f.fileName); - if (scriptInfoToDetach) { - scriptInfoToDetach.detachFromProject(this); - } + this.detachScriptInfoFromProject(f.fileName); } } @@ -838,17 +841,21 @@ namespace ts.server { const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.partialSystem); scriptInfo.attachToProject(this); }, - removed => { - const scriptInfoToDetach = this.projectService.getScriptInfo(removed); - if (scriptInfoToDetach) { - scriptInfoToDetach.detachFromProject(this); - } - }); + removed => this.detachScriptInfoFromProject(removed) + ); const elapsed = timestamp() - start; this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`); return hasChanges; } + private detachScriptInfoFromProject(uncheckedFileName: string) { + const scriptInfoToDetach = this.projectService.getScriptInfo(uncheckedFileName); + if (scriptInfoToDetach) { + scriptInfoToDetach.detachFromProject(this); + this.resolutionCache.removeResolutionsOfFile(scriptInfoToDetach.path); + } + } + private addMissingFileWatcher(missingFilePath: Path) { const fileWatcher = this.projectService.watchFile( this.projectService.host, diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index bc3e11baa0e..3dfa0cc7e5f 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -282,7 +282,7 @@ namespace ts.server { } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways - p.removeFile(this, /*detachFromProjects*/ false); + p.removeFile(this, /*fileExists*/ false, /*detachFromProjects*/ false); // If the info was for the external or configured project's root, // add missing file as the root if (isInfoRoot && p.projectKind !== ProjectKind.Inferred) { From de28d02626ca2c91fd5ac8d4f57482eac5a4b6c4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 8 Sep 2017 15:14:03 -0700 Subject: [PATCH 100/144] Add test case to verify correct resolution file is picked when current resolution file is not removed but higher precedence file is created --- src/compiler/program.ts | 4 +- .../unittests/tsserverProjectSystem.ts | 53 +++++++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 4 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f74675dbf31..bd702edb27c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -825,7 +825,7 @@ namespace ts { const moduleName = moduleNames[i]; // If we want to reuse resolutions more aggressively, we can refine this to check for whether the // text of the corresponding modulenames has changed. - if (file === oldSourceFile) { + if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules.get(moduleName); if (oldResolvedModule) { if (isTraceEnabled(options, host)) { @@ -846,7 +846,7 @@ namespace ts { trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile); } } - else if (!hasInvalidatedResolution(oldProgramState.file.path)) { + else { resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, oldProgramState); } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index e9561c25ff9..7d49e82151f 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2306,6 +2306,59 @@ namespace ts.projectSystem { }); assert.isFalse(hasErrorMsg); }); + + it("Changed module resolution reflected when specifying files list", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: 'import classc from "file2"' + }; + const file2a: FileOrFolder = { + path: "/a/file2.ts", + content: "export classc { method2a() { return 10; } }" + }; + const file2: FileOrFolder = { + path: "/a/b/file2.ts", + content: "export classc { method2() { return 10; } }" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ files: [file1.path], compilerOptions: { module: "amd" } }) + }; + const files = [file1, file2a, configFile, libFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); + assert.isDefined(project); + checkProjectActualFiles(project, map(files, file => file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + const watchedRecursiveDirectories = getTypeRootsFromLocation("/a/b"); + watchedRecursiveDirectories.push("/a/b"); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + + files.push(file2); + host.reloadFS(files); + host.runQueuedTimeoutCallbacks(); + watchedRecursiveDirectories.pop(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, mapDefined(files, file => file === file1 ? undefined : file.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + + // On next file open the files file2a should be closed and not watched any more + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + checkProjectActualFiles(project, mapDefined(files, file => file === file2a ? undefined : file.path)); + checkWatchedFiles(host, [libFile.path, configFile.path]); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); + + }); }); describe("Proper errors", () => { diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 724326d3eb6..1eee6c57f12 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -130,9 +130,9 @@ namespace ts.TestFSWithWatch { } export function checkFileNames(caption: string, actualFileNames: string[], expectedFileNames: string[]) { - assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${JSON.stringify(expectedFileNames)}, got ${actualFileNames}`); + assert.equal(actualFileNames.length, expectedFileNames.length, `${caption}: incorrect actual number of files, expected ${expectedFileNames}, got ${actualFileNames}`); for (const f of expectedFileNames) { - assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${JSON.stringify(actualFileNames)}`); + assert.isTrue(contains(actualFileNames, f), `${caption}: expected to find ${f} in ${actualFileNames}`); } } From 5739b6897a3bad40ff5551dd4df39ea4fe32924b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 8 Sep 2017 18:02:10 -0700 Subject: [PATCH 101/144] Do not create map just to store empty reference files. Also update file as changed if file text is same but it had invalidated resolution --- src/compiler/builder.ts | 62 ++++++++++++++++--- src/compiler/utilities.ts | 48 +++++++------- .../unittests/tsserverProjectSystem.ts | 2 +- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 68fb36b9144..40f3cdadb64 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -48,6 +48,7 @@ namespace ts { addScriptInfo(program: Program, sourceFile: SourceFile): void; removeScriptInfo(path: Path): void; updateScriptInfo(program: Program, sourceFile: SourceFile): void; + updaterScriptInfoWithSameVersion(program: Program, sourceFile: SourceFile): boolean; /** * Gets the files affected by the script info which has updated shape from the known one */ @@ -143,11 +144,15 @@ namespace ts { } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { - if (existingInfo.version !== sourceFile.version || hasInvalidatedResolution(sourceFile.path)) { + if (existingInfo.version !== sourceFile.version) { registerChangedFile(sourceFile.path, sourceFile.fileName); existingInfo.version = sourceFile.version; emitHandler.updateScriptInfo(program, sourceFile); } + else if (hasInvalidatedResolution(sourceFile.path) && + emitHandler.updaterScriptInfoWithSameVersion(program, sourceFile)) { + registerChangedFile(sourceFile.path, sourceFile.fileName); + } } function ensureProgramGraph(program: Program) { @@ -338,8 +343,8 @@ namespace ts { /** * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true */ - function getReferencedFiles(program: Program, sourceFile: SourceFile): Map { - const referencedFiles = createMap(); + function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { + let referencedFiles: Map | undefined; // We need to use a set here since the code can contain the same import twice, // but that will only be one dependency. @@ -351,7 +356,7 @@ namespace ts { if (symbol && symbol.declarations && symbol.declarations[0]) { const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); if (declarationSourceFile) { - referencedFiles.set(declarationSourceFile.path, true); + addReferencedFile(declarationSourceFile.path); } } } @@ -362,7 +367,7 @@ namespace ts { if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { for (const referencedFile of sourceFile.referencedFiles) { const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - referencedFiles.set(referencedPath, true); + addReferencedFile(referencedPath); } } @@ -375,11 +380,18 @@ namespace ts { const fileName = resolvedTypeReferenceDirective.resolvedFileName; const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); - referencedFiles.set(typeFilePath, true); + addReferencedFile(typeFilePath); }); } return referencedFiles; + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } } /** @@ -402,6 +414,7 @@ namespace ts { addScriptInfo: noop, removeScriptInfo: noop, updateScriptInfo: noop, + updaterScriptInfoWithSameVersion: returnFalse, getFilesAffectedByUpdatedShape }; @@ -421,12 +434,45 @@ namespace ts { return { addScriptInfo: setReferences, removeScriptInfo, - updateScriptInfo: setReferences, + updateScriptInfo: updateReferences, + updaterScriptInfoWithSameVersion: updateReferencesTrackingChangedReferences, getFilesAffectedByUpdatedShape }; function setReferences(program: Program, sourceFile: SourceFile) { - references.set(sourceFile.path, getReferencedFiles(program, sourceFile)); + const newReferences = getReferencedFiles(program, sourceFile); + if (newReferences) { + references.set(sourceFile.path, getReferencedFiles(program, sourceFile)); + } + } + + function updateReferences(program: Program, sourceFile: SourceFile) { + const newReferences = getReferencedFiles(program, sourceFile); + if (newReferences) { + references.set(sourceFile.path, newReferences); + } + else { + references.delete(sourceFile.path); + } + } + + function updateReferencesTrackingChangedReferences(program: Program, sourceFile: SourceFile) { + const newReferences = getReferencedFiles(program, sourceFile); + if (!newReferences) { + // Changed if we had references + return references.delete(sourceFile.path); + } + + const oldReferences = references.get(sourceFile.path); + references.set(sourceFile.path, newReferences); + if (!oldReferences || oldReferences.size !== newReferences.size) { + return true; + } + + // If there are any new references that werent present previously there is change + return forEachEntry(newReferences, (_true, referencedPath) => !oldReferences.delete(referencedPath)) || + // Otherwise its changed if there are more references previously than now + !!oldReferences.size; } function removeScriptInfo(removedFilePath: Path) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5785692017d..868e9e9420e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3513,34 +3513,28 @@ namespace ts { * Mutates the map with newMap such that keys in map will be same as newMap. */ export function mutateMap(map: Map, newMap: ReadonlyMap, options: MutateMapOptions) { - // If there are new values update them - if (newMap) { - const { createNewValue, onDeleteValue, onExistingValue } = options; - // Needs update - map.forEach((existingValue, key) => { - const valueInNewMap = newMap.get(key); - // Not present any more in new map, remove it - if (valueInNewMap === undefined) { - map.delete(key); - onDeleteValue(existingValue, key); - } - // If present notify about existing values - else if (onExistingValue) { - onExistingValue(existingValue, valueInNewMap, key); - } - }); + const { createNewValue, onDeleteValue, onExistingValue } = options; + // Needs update + map.forEach((existingValue, key) => { + const valueInNewMap = newMap.get(key); + // Not present any more in new map, remove it + if (valueInNewMap === undefined) { + map.delete(key); + onDeleteValue(existingValue, key); + } + // If present notify about existing values + else if (onExistingValue) { + onExistingValue(existingValue, valueInNewMap, key); + } + }); - // Add new values that are not already present - newMap.forEach((valueInNewMap, key) => { - if (!map.has(key)) { - // New values - map.set(key, createNewValue(key, valueInNewMap)); - } - }); - } - else { - clearMap(map, options.onDeleteValue); - } + // Add new values that are not already present + newMap.forEach((valueInNewMap, key) => { + if (!map.has(key)) { + // New values + map.set(key, createNewValue(key, valueInNewMap)); + } + }); } /** diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 7d49e82151f..7752d26e208 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -5275,7 +5275,7 @@ namespace ts.projectSystem { watchedRecursiveDirectories.length = 2; verifyProject(); - const changedFiles = limitHit ? [file1.path, file2.path, file3.path, libFile.path] : [file1.path, file2.path]; + const changedFiles = [file1.path, file2.path]; verifyProjectChangedEventHandler([{ eventName: server.ProjectChangedEvent, data: { From b536f9dadedd2ea279ba6dabe007cd3033984e6d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 12 Sep 2017 12:09:06 -0700 Subject: [PATCH 102/144] Should not remove the reused resolutions in the file when file contents have not changed. --- src/compiler/program.ts | 8 +++++--- src/compiler/resolutionCache.ts | 11 ++++++----- src/compiler/types.ts | 2 +- src/compiler/watchedProgram.ts | 4 ++-- src/server/project.ts | 4 ++-- src/services/services.ts | 2 +- src/services/types.ts | 2 +- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index f1683d1e4db..5ff1c1e2b3b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -595,11 +595,11 @@ namespace ts { let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression; let moduleResolutionCache: ModuleResolutionCache; - let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; + let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModuleFull[]; const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; if (host.resolveModuleNames) { - resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => { + resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile, reusedNames).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { return resolved as ResolvedModuleFull; @@ -820,6 +820,7 @@ namespace ts { * * ResolvedModuleFull instance: can be reused. */ let result: ResolvedModuleFull[]; + let reusedNames: string[]; /** A transient placeholder used to mark predicted resolution in the result list. */ const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {}; @@ -835,6 +836,7 @@ namespace ts { trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile); } (result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule; + (reusedNames || (reusedNames = [])).push(moduleName); continue; } } @@ -863,7 +865,7 @@ namespace ts { } const resolutions = unknownModuleNames && unknownModuleNames.length - ? resolveModuleNamesWorker(unknownModuleNames, containingFile) + ? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames) : emptyArray; // Combine results of resolutions and predicted results diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 71ef37a9a37..c69caf15f07 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -8,7 +8,7 @@ namespace ts { startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; - resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; @@ -207,6 +207,7 @@ namespace ts { perDirectoryCache: Map>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, + reusedNames: string[] | undefined, logChanges: boolean): R[] { const path = resolutionHost.toPath(containingFile); @@ -253,7 +254,7 @@ namespace ts { // Stop watching and remove the unused name resolutionsInFile.forEach((resolution, name) => { - if (!seenNamesInFile.has(name)) { + if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) { stopWatchFailedLookupLocationOfResolution(resolution); resolutionsInFile.delete(name); } @@ -285,16 +286,16 @@ namespace ts { typeDirectiveNames, containingFile, resolvedTypeReferenceDirectives, perDirectoryResolvedTypeReferenceDirectives, resolveTypeReferenceDirective, getResolvedTypeReferenceDirective, - /*logChanges*/ false + /*reusedNames*/ undefined, /*logChanges*/ false ); } - function resolveModuleNames(moduleNames: string[], containingFile: string, logChanges: boolean): ResolvedModuleFull[] { + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[] { return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, resolveModuleName, getResolvedModule, - logChanges + reusedNames, logChanges ); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ef15e3fdd1e..d473f2441bd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4138,7 +4138,7 @@ namespace ts { * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just * 'throw new Error("NotImplemented")' */ - resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 67a1a943bf6..7d7b4b1b1d3 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -396,8 +396,8 @@ namespace ts { return partialSystem.getDirectories(path); } - function resolveModuleNames(moduleNames: string[], containingFile: string) { - return resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false); + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]) { + return resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); } function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { diff --git a/src/server/project.ts b/src/server/project.ts index c52f520b8a4..4a66a4064e0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -325,8 +325,8 @@ namespace ts.server { return !this.isWatchedMissingFile(path) && this.partialSystem.fileExists(file); } - resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { - return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[] { + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ true); } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { diff --git a/src/services/services.ts b/src/services/services.ts index e8f073f4a17..08d5b007e87 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1177,7 +1177,7 @@ namespace ts { } if (host.resolveModuleNames) { - compilerHost.resolveModuleNames = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile); + compilerHost.resolveModuleNames = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames); } if (host.resolveTypeReferenceDirectives) { compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile) => { diff --git a/src/services/types.ts b/src/services/types.ts index e63651b3371..1fa06796065 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -183,7 +183,7 @@ namespace ts { * if implementation is omitted then language service will use built-in module resolution logic and get answers to * host specific questions using 'getScriptSnapshot'. */ - resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; hasInvalidatedResolution?: HasInvalidatedResolution; hasChangedAutomaticTypeDirectiveNames?(): boolean; From 4f7c0e5e1ce6085cd71976cd4a4bf301f328f5c4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 12 Sep 2017 18:11:45 -0700 Subject: [PATCH 103/144] Simplify event sent on background project update since its anyways just to update the error list --- src/compiler/builder.ts | 27 -- .../unittests/tsserverProjectSystem.ts | 249 ++++++++---------- src/server/editorServices.ts | 40 ++- src/server/project.ts | 6 - src/server/protocol.ts | 24 +- src/server/session.ts | 22 +- 6 files changed, 139 insertions(+), 229 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index b4ebbc76bd7..e7bcb28d820 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -35,8 +35,6 @@ namespace ts { /** Emit the changed files and clear the cache of the changed files */ emitChangedFiles(program: Program): EmitOutputDetailed[]; - /** Get the changed files since last query and then clear the cache of changed files */ - getChangedProgramFiles(program: Program): ChangedProgramFiles; /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): Diagnostic[]; @@ -103,7 +101,6 @@ namespace ts { getFilesAffectedBy, emitFile, emitChangedFiles, - getChangedProgramFiles, getSemanticDiagnostics, clear }; @@ -273,30 +270,6 @@ namespace ts { return diagnostics || emptyArray; } - function getChangedProgramFiles(program: Program): ChangedProgramFiles { - ensureProgramGraph(program); - - let filesToEmit: string[]; - const changedFiles = createMap(); - enumerateChangedFilesEmitOutput(program, /*emitOnlyDtsFiles*/ true, - // All the changed files are required to get diagnostics - (changedFileName, changedFilePath) => addFileForDiagnostics(changedFileName, changedFilePath), - // Emitted file is for emit as well as diagnostic - (_emitOutput, sourceFile) => { - (filesToEmit || (filesToEmit = [])).push(sourceFile.fileName); - addFileForDiagnostics(sourceFile.fileName, sourceFile.path); - }); - changedFileNames.clear(); - return { - filesToEmit: filesToEmit || emptyArray, - changedFiles: arrayFrom(changedFiles.values()) - }; - - function addFileForDiagnostics(fileName: string, path: Path) { - changedFiles.set(path, fileName); - } - } - function clear() { isModuleEmit = undefined; emitHandler = undefined; diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index f253eca704f..d4126d603f2 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2158,7 +2158,7 @@ namespace ts.projectSystem { const session = createSession(host, { canUseEvents: true, eventHandler: e => { - if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectChangedEvent || e.eventName === server.ProjectInfoTelemetryEvent) { + if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent) { return; } assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent); @@ -4819,7 +4819,7 @@ namespace ts.projectSystem { }); }); - describe("ProjectChangedEvent", () => { + describe("ProjectsChangedInBackground", () => { function verifyFiles(caption: string, actual: ReadonlyArray, expected: ReadonlyArray) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); const seen = createMap(); @@ -4830,7 +4830,7 @@ namespace ts.projectSystem { }); } - function createVerifyInitialOpen(session: TestSession, verifyProjectChangedEventHandler: (events: server.ProjectChangedEvent[]) => void) { + function createVerifyInitialOpen(session: TestSession, verifyProjectsUpdatedInBackgroundEventHandler: (events: server.ProjectsUpdatedInBackgroundEvent[]) => void) { return (file: FileOrFolder) => { session.executeCommandSeq({ command: server.CommandNames.Open, @@ -4838,17 +4838,17 @@ namespace ts.projectSystem { file: file.path } }); - verifyProjectChangedEventHandler([]); + verifyProjectsUpdatedInBackgroundEventHandler([]); }; } - interface ProjectChangeEventVerifier { + interface ProjectsUpdatedInBackgroundEventVerifier { session: TestSession; - verifyProjectChangedEventHandler(events: server.ProjectChangedEvent[]): void; + verifyProjectsUpdatedInBackgroundEventHandler(events: server.ProjectsUpdatedInBackgroundEvent[]): void; verifyInitialOpen(file: FileOrFolder): void; } - function verifyProjectChangedEvent(createSession: (host: TestServerHost) => ProjectChangeEventVerifier) { + function verifyProjectsUpdatedInBackgroundEvent(createSession: (host: TestServerHost) => ProjectsUpdatedInBackgroundEventVerifier) { it("when adding new file", () => { const commonFile1: FileOrFolder = { path: "/a/b/file1.ts", @@ -4866,32 +4866,26 @@ namespace ts.projectSystem { path: "/a/b/tsconfig.json", content: `{}` }; + const openFiles = [commonFile1.path]; const host = createServerHost([commonFile1, libFile, configFile]); - const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host, ); - const projectService = session.getProjectService(); + const { verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host, ); verifyInitialOpen(commonFile1); host.reloadFS([commonFile1, libFile, configFile, commonFile2]); host.runQueuedTimeoutCallbacks(); - // Since this is first event - const project = projectService.configuredProjects.get(configFile.path); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles: [libFile.path, commonFile1.path, commonFile2.path], - filesToEmit: [commonFile1.path, commonFile2.path] + openFiles } }]); host.reloadFS([commonFile1, commonFile2, libFile, configFile, commonFile3]); host.runQueuedTimeoutCallbacks(); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles: [commonFile3.path], - filesToEmit: [commonFile3.path] + openFiles } }]); }); @@ -4914,36 +4908,30 @@ namespace ts.projectSystem { content: "export let y = 1" }; + const openFiles = [f1.path]; const files = [f1, config, libFile]; const host = createServerHost(files); - const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); - const projectService = session.getProjectService(); + const { verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); verifyInitialOpen(f1); files.push(f2); host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - // Since this is first event - const project = projectService.configuredProjects.get(config.path); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles: [libFile.path, f1.path, f2.path], - filesToEmit: [f1.path, f2.path] + openFiles } }]); f2.content = "export let x = 11"; host.reloadFS(files); host.runQueuedTimeoutCallbacks(); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles: [f2.path], - filesToEmit: [f2.path] + openFiles } }]); } @@ -4970,14 +4958,12 @@ namespace ts.projectSystem { interface InitialStateParams { /** custom config file options */ configObj?: any; - /** list of files emitted/changed on first update graph */ - firstCompilationEmitFiles?: string[]; /** Additional files and folders to add */ getAdditionalFileOrFolder?(): FileOrFolder[]; /** initial list of files to reload in fs and first file in this list being the file to open */ firstReloadFileList?: string[]; } - function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList, firstCompilationEmitFiles }: InitialStateParams = {}) { + function getInitialState({ configObj = {}, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) { const moduleFile1: FileOrFolder = { path: moduleFile1Path, content: "export function Foo() { };", @@ -5015,20 +5001,19 @@ namespace ts.projectSystem { const host = createServerHost([filesToReload[0], configFile]); // Initial project creation - const { session, verifyProjectChangedEventHandler, verifyInitialOpen } = createSession(host); - const projectService = session.getProjectService(); + const { session, verifyProjectsUpdatedInBackgroundEventHandler, verifyInitialOpen } = createSession(host); + const openFiles = [filesToReload[0].path]; verifyInitialOpen(filesToReload[0]); // Since this is first event, it will have all the files - const firstFilesExpected = firstCompilationEmitFiles && getFiles(firstCompilationEmitFiles) || filesToReload; - verifyProjectChangedEvent(firstFilesExpected, filesToReload); + verifyProjectsUpdatedInBackgroundEvent(filesToReload); return { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile, files, updateContentOfOpenFile, - verifyProjectChangedEvent, - verifyAffectedAllFiles, + verifyNoProjectsUpdatedInBackgroundEvent, + verifyProjectsUpdatedInBackgroundEvent }; function getFiles(filelist: string[]) { @@ -5039,28 +5024,21 @@ namespace ts.projectSystem { return find(files, file => file.path === fileName); } - function verifyAffectedAllFiles() { - verifyProjectChangedEvent(filter(files, f => f !== libFile)); - } - - function verifyProjectChangedEvent(filesToEmit: FileOrFolder[], filesToReload?: FileOrFolder[], additionalChangedFiles?: FileOrFolder[]) { + function verifyNoProjectsUpdatedInBackgroundEvent(filesToReload?: FileOrFolder[]) { host.reloadFS(filesToReload || files); host.runQueuedTimeoutCallbacks(); - if (filesToEmit.length) { - const project = projectService.configuredProjects.get(configFile.path); - const changedFiles = mapDefined(additionalChangedFiles ? filesToEmit.concat(additionalChangedFiles) : filesToEmit, f => f !== configFile ? f.path : undefined); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, - data: { - project, - changedFiles, - filesToEmit: mapDefined(filesToEmit, f => f !== libFile && f !== configFile ? f.path : undefined) - } - }]); - } - else { - verifyProjectChangedEventHandler([]); - } + verifyProjectsUpdatedInBackgroundEventHandler([]); + } + + function verifyProjectsUpdatedInBackgroundEvent(filesToReload?: FileOrFolder[]) { + host.reloadFS(filesToReload || files); + host.runQueuedTimeoutCallbacks(); + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, + data: { + openFiles + } + }]); } function updateContentOfOpenFile(file: FileOrFolder, newContent: string) { @@ -5080,35 +5058,35 @@ namespace ts.projectSystem { } it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { - const { moduleFile1, file1Consumer1, file1Consumer2, verifyProjectChangedEvent } = getInitialState(); + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + verifyProjectsUpdatedInBackgroundEvent(); // Change the content of moduleFile1 to `export var T: number;export function Foo() { console.log('hi'); };` moduleFile1.content = `export var T: number;export function Foo() { console.log('hi'); };`; - verifyProjectChangedEvent([moduleFile1]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should be up-to-date with the reference map changes", () => { - const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState(); + const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyProjectsUpdatedInBackgroundEvent, verifyNoProjectsUpdatedInBackgroundEvent } = getInitialState(); // Change file1Consumer1 content to `export let y = Foo();` updateContentOfOpenFile(file1Consumer1, "export let y = Foo();"); - verifyProjectChangedEvent([]); + verifyNoProjectsUpdatedInBackgroundEvent(); // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); + verifyProjectsUpdatedInBackgroundEvent(); // Add the import statements back to file1Consumer1 updateContentOfOpenFile(file1Consumer1, `import {Foo} from "./moduleFile1";let y = Foo();`); - verifyProjectChangedEvent([]); + verifyNoProjectsUpdatedInBackgroundEvent(); // Change the content of moduleFile1 to `export var T: number;export var T2: string;export function Foo() { };` moduleFile1.content = `export var T: number;export var T2: string;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer2, file1Consumer1]); + verifyProjectsUpdatedInBackgroundEvent(); // Multiple file edits in one go: @@ -5116,69 +5094,68 @@ namespace ts.projectSystem { // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` updateContentOfOpenFile(file1Consumer1, `export let y = Foo();`); moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should be up-to-date with deleted files", () => { - const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent } = getInitialState(); + const { moduleFile1, file1Consumer2, files, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); // Change the content of moduleFile1 to `export var T: number;export function Foo() { };` moduleFile1.content = `export var T: number;export function Foo() { };`; // Delete file1Consumer2 const filesToLoad = filter(files, file => file !== file1Consumer2); - verifyProjectChangedEvent([moduleFile1, file1Consumer1], filesToLoad, [file1Consumer2]); + verifyProjectsUpdatedInBackgroundEvent(filesToLoad); }); it("should be up-to-date with newly created files", () => { - const { moduleFile1, file1Consumer1, file1Consumer2, files, verifyProjectChangedEvent, } = getInitialState(); + const { moduleFile1, files, verifyProjectsUpdatedInBackgroundEvent, } = getInitialState(); const file1Consumer3: FileOrFolder = { path: "/a/b/file1Consumer3.ts", content: `import {Foo} from "./moduleFile1"; let y = Foo();` }; moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer3, file1Consumer2], files.concat(file1Consumer3)); + verifyProjectsUpdatedInBackgroundEvent(files.concat(file1Consumer3)); }); it("should detect changes in non-root files", () => { - const { moduleFile1, file1Consumer1, verifyProjectChangedEvent } = getInitialState({ + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ configObj: { files: [file1Consumer1Path] }, - firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path, libFile.path] }); moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1]); + verifyProjectsUpdatedInBackgroundEvent(); // change file1 internal, and verify only file1 is affected moduleFile1.content += "var T1: number;"; - verifyProjectChangedEvent([moduleFile1]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should return all files if a global file changed shape", () => { - const { globalFile3, verifyAffectedAllFiles } = getInitialState(); + const { globalFile3, verifyProjectsUpdatedInBackgroundEvent } = getInitialState(); globalFile3.content += "var T2: string;"; - verifyAffectedAllFiles(); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should always return the file itself if '--isolatedModules' is specified", () => { - const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should always return the file itself if '--out' or '--outFile' is specified", () => { const outFilePath = "/a/b/out.js"; - const { moduleFile1, verifyProjectChangedEvent } = getInitialState({ + const { moduleFile1, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ configObj: { compilerOptions: { module: "system", outFile: outFilePath } } }); moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should return cascaded affected file list", () => { @@ -5186,21 +5163,21 @@ namespace ts.projectSystem { path: "/a/b/file1Consumer1Consumer1.ts", content: `import {y} from "./file1Consumer1";` }; - const { moduleFile1, file1Consumer1, file1Consumer2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ + const { moduleFile1, file1Consumer1, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] }); updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T: number;"); - verifyProjectChangedEvent([]); + verifyNoProjectsUpdatedInBackgroundEvent(); // Doesnt change the shape of file1Consumer1 moduleFile1.content = `export var T: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); + verifyProjectsUpdatedInBackgroundEvent(); // Change both files before the timeout updateContentOfOpenFile(file1Consumer1, file1Consumer1.content + "export var T2: number;"); moduleFile1.content = `export var T2: number;export function Foo() { };`; - verifyProjectChangedEvent([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1]); + verifyProjectsUpdatedInBackgroundEvent(); }); it("should work fine for files with circular references", () => { @@ -5216,13 +5193,13 @@ namespace ts.projectSystem { /// export var t2 = 10;` }; - const { configFile, verifyProjectChangedEvent } = getInitialState({ + const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ getAdditionalFileOrFolder: () => [file1, file2], firstReloadFileList: [file1.path, libFile.path, file2.path, configFilePath] }); file2.content += "export var t3 = 10;"; - verifyProjectChangedEvent([file1, file2], [file1, file2, libFile, configFile]); + verifyProjectsUpdatedInBackgroundEvent([file1, file2, libFile, configFile]); }); it("should detect removed code file", () => { @@ -5232,12 +5209,12 @@ namespace ts.projectSystem { /// export var x = Foo();` }; - const { configFile, verifyProjectChangedEvent, moduleFile1 } = getInitialState({ + const { configFile, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ getAdditionalFileOrFolder: () => [referenceFile1], firstReloadFileList: [referenceFile1.path, libFile.path, moduleFile1Path, configFilePath] }); - verifyProjectChangedEvent([referenceFile1], [libFile, referenceFile1, configFile], [moduleFile1]); + verifyProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); }); it("should detect non-existing code file", () => { @@ -5247,16 +5224,16 @@ namespace ts.projectSystem { /// export var x = Foo();` }; - const { configFile, moduleFile2, updateContentOfOpenFile, verifyProjectChangedEvent } = getInitialState({ + const { configFile, moduleFile2, updateContentOfOpenFile, verifyNoProjectsUpdatedInBackgroundEvent, verifyProjectsUpdatedInBackgroundEvent } = getInitialState({ getAdditionalFileOrFolder: () => [referenceFile1], firstReloadFileList: [referenceFile1.path, libFile.path, configFilePath] }); updateContentOfOpenFile(referenceFile1, referenceFile1.content + "export var yy = Foo();"); - verifyProjectChangedEvent([], [libFile, referenceFile1, configFile]); + verifyNoProjectsUpdatedInBackgroundEvent([libFile, referenceFile1, configFile]); // Create module File2 and see both files are saved - verifyProjectChangedEvent([referenceFile1, moduleFile2], [libFile, moduleFile2, referenceFile1, configFile]); + verifyProjectsUpdatedInBackgroundEvent([libFile, moduleFile2, referenceFile1, configFile]); }); }); @@ -5280,9 +5257,10 @@ namespace ts.projectSystem { }; const projectFiles = [file1, file3, libFile, configFile]; + const openFiles = [file1.path]; const watchedRecursiveDirectories = ["/a/b/project", "/a/b/node_modules", "/a/node_modules", "/node_modules"]; const host = createServerHost(projectFiles); - const { session, verifyInitialOpen, verifyProjectChangedEventHandler } = createSession(host); + const { session, verifyInitialOpen, verifyProjectsUpdatedInBackgroundEventHandler } = createSession(host); const projectService = session.getProjectService(); verifyInitialOpen(file1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); @@ -5298,12 +5276,10 @@ namespace ts.projectSystem { // Since this is first event verifyProject(); - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles: [libFile.path, file1.path, file3.path], - filesToEmit: [file1.path, file3.path] + openFiles } }]); @@ -5313,13 +5289,10 @@ namespace ts.projectSystem { watchedRecursiveDirectories.length = 2; verifyProject(); - const changedFiles = [file1.path, file2.path]; - verifyProjectChangedEventHandler([{ - eventName: server.ProjectChangedEvent, + verifyProjectsUpdatedInBackgroundEventHandler([{ + eventName: server.ProjectsUpdatedInBackgroundEvent, data: { - project, - changedFiles, - filesToEmit: changedFiles + openFiles } }]); @@ -5341,13 +5314,13 @@ namespace ts.projectSystem { } describe("when event handler is set in the session", () => { - verifyProjectChangedEvent(createSessionWithProjectChangedEventHandler); + verifyProjectsUpdatedInBackgroundEvent(createSessionWithProjectChangedEventHandler); - function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectChangeEventVerifier { - const projectChangedEvents: server.ProjectChangedEvent[] = []; + function createSessionWithProjectChangedEventHandler(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { + const projectChangedEvents: server.ProjectsUpdatedInBackgroundEvent[] = []; const session = createSession(host, { eventHandler: e => { - if (e.eventName === server.ProjectChangedEvent) { + if (e.eventName === server.ProjectsUpdatedInBackgroundEvent) { projectChangedEvents.push(e); } } @@ -5355,34 +5328,24 @@ namespace ts.projectSystem { return { session, - verifyProjectChangedEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) }; - function eventToString(event: server.ProjectChangedEvent) { - const eventToModify = event && { - eventName: event.eventName, - data: { - project: event.data.project.getProjectName(), - changedFiles: event.data.changedFiles, - filesToEmit: event.data.filesToEmit - } - }; - return JSON.stringify(eventToModify); + function eventToString(event: server.ProjectsUpdatedInBackgroundEvent) { + return JSON.stringify(event && { eventName: event.eventName, data: event.data }); } - function eventsToString(events: ReadonlyArray) { + function eventsToString(events: ReadonlyArray) { return "[" + map(events, eventToString).join(",") + "]"; } - function verifyProjectChangedEventHandler(expectedEvents: ReadonlyArray) { + function verifyProjectsUpdatedInBackgroundEventHandler(expectedEvents: ReadonlyArray) { assert.equal(projectChangedEvents.length, expectedEvents.length, `Incorrect number of events Actual: ${eventsToString(projectChangedEvents)} Expected: ${eventsToString(expectedEvents)}`); forEach(projectChangedEvents, (actualEvent, i) => { const expectedEvent = expectedEvents[i]; assert.strictEqual(actualEvent.eventName, expectedEvent.eventName); - assert.strictEqual(actualEvent.data.project, expectedEvent.data.project); - verifyFiles("changedFiles", actualEvent.data.changedFiles, expectedEvent.data.changedFiles); - verifyFiles("filesToEmit", actualEvent.data.filesToEmit, expectedEvent.data.filesToEmit); + verifyFiles("openFiles", actualEvent.data.openFiles, expectedEvent.data.openFiles); }); // Verified the events, reset them @@ -5392,41 +5355,37 @@ namespace ts.projectSystem { }); describe("when event handler is not set but session is created with canUseEvents = true", () => { - verifyProjectChangedEvent(createSessionThatUsesEvents); + verifyProjectsUpdatedInBackgroundEvent(createSessionThatUsesEvents); - function createSessionThatUsesEvents(host: TestServerHost): ProjectChangeEventVerifier { + function createSessionThatUsesEvents(host: TestServerHost): ProjectsUpdatedInBackgroundEventVerifier { const session = createSession(host, { canUseEvents: true }); return { session, - verifyProjectChangedEventHandler, - verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectChangedEventHandler) + verifyProjectsUpdatedInBackgroundEventHandler, + verifyInitialOpen: createVerifyInitialOpen(session, verifyProjectsUpdatedInBackgroundEventHandler) }; - function verifyProjectChangedEventHandler(expected: ReadonlyArray) { - const expectedEvents: protocol.ProjectChangedEventBody[] = map(expected, e => { + function verifyProjectsUpdatedInBackgroundEventHandler(expected: ReadonlyArray) { + const expectedEvents: protocol.ProjectsUpdatedInBackgroundEventBody[] = map(expected, e => { return { - projectName: e.data.project.getProjectName(), - changedFiles: e.data.changedFiles, - fileNamesToEmit: e.data.filesToEmit + openFiles: e.data.openFiles }; }); const outputEventRegex = /Content\-Length: [\d]+\r\n\r\n/; - const events: protocol.ProjectStructureChangedEvent[] = filter( + const events: protocol.ProjectsUpdatedInBackgroundEvent[] = filter( map( host.getOutput(), s => convertToObject( ts.parseJsonText("json.json", s.replace(outputEventRegex, "")), [] ) ), - e => e.event === server.ProjectChangedEvent + e => e.event === server.ProjectsUpdatedInBackgroundEvent ); assert.equal(events.length, expectedEvents.length, `Incorrect number of events Actual: ${map(events, e => e.body)} Expected: ${expectedEvents}`); forEach(events, (actualEvent, i) => { const expectedEvent = expectedEvents[i]; - assert.strictEqual(actualEvent.body.projectName, expectedEvent.projectName); - verifyFiles("changedFiles", actualEvent.body.changedFiles, expectedEvent.changedFiles); - verifyFiles("fileNamesToEmit", actualEvent.body.fileNamesToEmit, expectedEvent.fileNamesToEmit); + verifyFiles("openFiles", actualEvent.body.openFiles, expectedEvent.openFiles); }); // Verified the events, reset them diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 84f8b5c13f8..f7f935049f3 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -9,14 +9,14 @@ namespace ts.server { export const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; - export const ProjectChangedEvent = "projectChanged"; + export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground"; export const ConfigFileDiagEvent = "configFileDiag"; export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; export const ProjectInfoTelemetryEvent = "projectInfo"; - export interface ProjectChangedEvent { - eventName: typeof ProjectChangedEvent; - data: { project: Project; filesToEmit: string[]; changedFiles: string[] }; + export interface ProjectsUpdatedInBackgroundEvent { + eventName: typeof ProjectsUpdatedInBackgroundEvent; + data: { openFiles: string[]; }; } export interface ConfigFileDiagEvent { @@ -76,7 +76,7 @@ namespace ts.server { readonly dts: number; } - export type ProjectServiceEvent = ProjectChangedEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; + export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; export interface ProjectServiceEventHandler { (event: ProjectServiceEvent): void; @@ -517,9 +517,14 @@ namespace ts.server { if (this.pendingProjectUpdates.size !== 0) { this.delayInferredProjectsRefresh(); } - else if (this.pendingInferredProjectUpdate) { - this.pendingInferredProjectUpdate = false; - this.refreshInferredProjects(); + else { + if (this.pendingInferredProjectUpdate) { + this.pendingInferredProjectUpdate = false; + this.refreshInferredProjects(); + } + // Send the event to notify that there were background project updates + // send current list of open files + this.sendProjectsUpdatedInBackgroundEvent(); } }); } @@ -531,27 +536,18 @@ namespace ts.server { if (this.pendingProjectUpdates.delete(projectName)) { project.updateGraph(); } - // Send the update event to notify about the project changes - this.sendProjectChangedEvent(project); }); } - private sendProjectChangedEvent(project: Project) { - if (project.isClosed() || !this.eventHandler || !project.languageServiceEnabled) { + private sendProjectsUpdatedInBackgroundEvent() { + if (!this.eventHandler) { return; } - const { filesToEmit, changedFiles } = project.getChangedFiles(); - if (changedFiles.length === 0) { - return; - } - - const event: ProjectChangedEvent = { - eventName: ProjectChangedEvent, + const event: ProjectsUpdatedInBackgroundEvent = { + eventName: ProjectsUpdatedInBackgroundEvent, data: { - project, - filesToEmit: filesToEmit as string[], - changedFiles: changedFiles as string[] + openFiles: this.openFiles.map(f => f.fileName) } }; this.eventHandler(event); diff --git a/src/server/project.ts b/src/server/project.ts index 4a66a4064e0..3e2643fdb6a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -457,12 +457,6 @@ namespace ts.server { return !emitSkipped; } - getChangedFiles() { - Debug.assert(this.languageServiceEnabled); - this.ensureBuilder(); - return this.builder.getChangedProgramFiles(this.program); - } - enableLanguageService() { if (this.languageServiceEnabled) { return; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index cf91905280b..3d07392bbe6 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2040,27 +2040,17 @@ namespace ts.server.protocol { languageServiceEnabled: boolean; } - export type ProjectChangedEventName = "projectChanged"; - export interface ProjectStructureChangedEvent extends Event { - event: ProjectChangedEventName; - body: ProjectChangedEventBody; + export type ProjectsUpdatedInBackgroundEventName = "projectsUpdatedInBackground"; + export interface ProjectsUpdatedInBackgroundEvent extends Event { + event: ProjectsUpdatedInBackgroundEventName; + body: ProjectsUpdatedInBackgroundEventBody; } - export interface ProjectChangedEventBody { + export interface ProjectsUpdatedInBackgroundEventBody { /** - * Project name that has changes + * Current set of open files */ - projectName: string; - - /** - * Minimum set of file names to emit - */ - fileNamesToEmit: string[]; - - /** - * List of files that have changed/added/removed or could have been affected by the changed files - */ - changedFiles: string[]; + openFiles: string[]; } /** diff --git a/src/server/session.ts b/src/server/session.ts index cad460bba7b..3e7cbf04024 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -332,9 +332,9 @@ namespace ts.server { private defaultEventHandler(event: ProjectServiceEvent) { switch (event.eventName) { - case ProjectChangedEvent: - const { project, filesToEmit, changedFiles } = event.data; - this.projectChangedEvent(project, filesToEmit, changedFiles); + case ProjectsUpdatedInBackgroundEvent: + const { openFiles } = event.data; + this.projectsUpdatedInBackgroundEvent(openFiles); break; case ConfigFileDiagEvent: const { triggerFile, configFileName: configFile, diagnostics } = event.data; @@ -364,21 +364,19 @@ namespace ts.server { } } - private projectChangedEvent(project: Project, fileNamesToEmit: string[], changedFiles: string[]): void { - this.projectService.logger.info(`got project changed event, updating diagnostics for ${changedFiles}`); - if (changedFiles.length) { - const checkList = this.createCheckList(changedFiles, project); + private projectsUpdatedInBackgroundEvent(openFiles: string[]): void { + this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`); + if (openFiles.length) { + const checkList = this.createCheckList(openFiles); // For now only queue error checking for open files. We can change this to include non open files as well this.errorCheck.startNew(next => this.updateErrorCheck(next, checkList, 100, /*requireOpen*/ true)); // Send project changed event - this.event({ - projectName: project.getProjectName(), - changedFiles, - fileNamesToEmit - }, "projectChanged"); + this.event({ + openFiles + }, "projectsUpdatedInBackground"); } } From 12f5dd85d71b6ffddec54d74a6875dd0b82bdd8e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 18 Sep 2017 06:33:47 -0700 Subject: [PATCH 104/144] Introduce --strictFunctionTypes mode --- src/compiler/checker.ts | 82 +++++++++++++++++++++++----- src/compiler/commandLineParser.ts | 7 +++ src/compiler/diagnosticMessages.json | 4 ++ src/compiler/types.ts | 10 ++++ 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 62d44d160a7..33d8b9249da 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -66,6 +66,7 @@ namespace ts { const noUnusedIdentifiers = !!compilerOptions.noUnusedLocals || !!compilerOptions.noUnusedParameters; const allowSyntheticDefaultImports = typeof compilerOptions.allowSyntheticDefaultImports !== "undefined" ? compilerOptions.allowSyntheticDefaultImports : modulekind === ModuleKind.System; const strictNullChecks = compilerOptions.strictNullChecks === undefined ? compilerOptions.strict : compilerOptions.strictNullChecks; + const strictFunctionTypes = compilerOptions.strictFunctionTypes === undefined ? compilerOptions.strict : compilerOptions.strictFunctionTypes; const noImplicitAny = compilerOptions.noImplicitAny === undefined ? compilerOptions.strict : compilerOptions.noImplicitAny; const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; @@ -2517,7 +2518,7 @@ namespace ts { return typeReferenceToTypeNode(type); } if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { - const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false); + const name = type.symbol ? symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ false) : createIdentifier("?"); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } @@ -6729,7 +6730,7 @@ namespace ts { } function getConstraintDeclaration(type: TypeParameter) { - return getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter).constraint; + return type.symbol && getDeclarationOfKind(type.symbol, SyntaxKind.TypeParameter).constraint; } function getConstraintFromTypeParameter(typeParameter: TypeParameter): Type { @@ -8541,6 +8542,9 @@ namespace ts { source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); } + const targetKind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + const strictVariance = strictFunctionTypes && targetKind !== SyntaxKind.MethodDeclaration && targetKind !== SyntaxKind.MethodSignature; + let result = Ternary.True; const sourceThisType = getThisTypeOfSignature(source); @@ -8548,7 +8552,7 @@ namespace ts { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { // void sources are assignable to anything. - const related = compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) + const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) || compareTypes(targetThisType, sourceThisType, reportErrors); if (!related) { if (reportErrors) { @@ -8582,7 +8586,7 @@ namespace ts { (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); const related = callbacks ? compareSignaturesRelated(targetSig, sourceSig, /*checkAsCallback*/ true, /*ignoreReturnTypes*/ false, reportErrors, errorReporter, compareTypes) : - !checkAsCallback && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); + !checkAsCallback && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); if (!related) { if (reportErrors) { errorReporter(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, @@ -9194,7 +9198,7 @@ namespace ts { return result; } - function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, reportErrors: boolean): Ternary { + function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, variances: Variance[], reportErrors: boolean): Ternary { const sources = source.typeArguments || emptyArray; const targets = target.typeArguments || emptyArray; if (sources.length !== targets.length && relation === identityRelation) { @@ -9203,11 +9207,34 @@ namespace ts { const length = sources.length <= targets.length ? sources.length : targets.length; let result = Ternary.True; for (let i = 0; i < length; i++) { - const related = isRelatedTo(sources[i], targets[i], reportErrors); - if (!related) { - return Ternary.False; + const variance = i < variances.length ? variances[i] : Variance.Covariant; + if (variance !== Variance.Omnivariant) { + const s = sources[i]; + const t = targets[i]; + let related = Ternary.True; + if (variance === Variance.Covariant) { + related = isRelatedTo(s, t, reportErrors); + } + else if (variance === Variance.Contravariant) { + related = isRelatedTo(t, s, reportErrors); + } + else if (variance === Variance.Bivariant) { + related = isRelatedTo(t, s, /*reportErrors*/ false); + if (!related) { + related = isRelatedTo(s, t, reportErrors); + } + } + else { + related = isRelatedTo(s, t, reportErrors); + if (related) { + related &= isRelatedTo(t, s, reportErrors); + } + } + if (!related) { + return Ternary.False; + } + result &= related; } - result &= related; } return result; } @@ -9371,9 +9398,15 @@ namespace ts { } else { if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { - // We have type references to same target type, see if relationship holds for all type arguments - if (result = typeArgumentsRelatedTo(source, target, reportErrors)) { - return result; + const variances = getVariances((source).target); + if (variances) { + // We have type references to same target type, see if relationship holds for all type arguments + if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { + return result; + } + if (variances !== emptyArray) { + return Ternary.False; + } } } // Even if relationship doesn't hold for unions, intersections, or generic type references, @@ -9785,6 +9818,29 @@ namespace ts { } } + function getVarianceType(type: GenericType, source: TypeParameter, target: Type) { + return createTypeReference(type, map(type.typeParameters, t => t === source ? target: t)); + } + + function getVariances(type: GenericType) { + const typeParameters = type.typeParameters || emptyArray; + let variances = type.variances; + if (!variances) { + variances = type.variances = []; + for (const tp of typeParameters) { + const superType = getVarianceType(type, tp, stringType); + const subType = getVarianceType(type, tp, emptyStringType); + let variance = (isTypeAssignableTo(subType, superType) ? Variance.Covariant : 0) | + (isTypeAssignableTo(superType, subType) ? Variance.Contravariant : 0); + if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, numberType), superType)) { + variance = Variance.Omnivariant; + } + variances.push(variance); + } + } + return variances.length === typeParameters.length ? variances : emptyArray; + } + function isUnconstrainedTypeParameter(type: Type) { return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(type); } @@ -18856,7 +18912,7 @@ namespace ts { const typeArgument = typeArguments[i]; result = result && checkTypeAssignableTo( typeArgument, - getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), + instantiateType(constraint, mapper), typeArgumentNodes[i], Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 54e5ee1d01d..b63442bc89a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -269,6 +269,13 @@ namespace ts { category: Diagnostics.Strict_Type_Checking_Options, description: Diagnostics.Enable_strict_null_checks }, + { + name: "strictFunctionTypes", + type: "boolean", + showInSimplifiedHelpView: true, + category: Diagnostics.Strict_Type_Checking_Options, + description: Diagnostics.Enable_strict_checking_of_function_types + }, { name: "noImplicitThis", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 8a76ddcda9a..3a9857e09ea 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3318,6 +3318,10 @@ "category": "Message", "code": 6185 }, + "Enable strict checking of function types.": { + "category": "Message", + "code": 6186 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5347d7caaf0..cddfcee6413 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3347,6 +3347,7 @@ namespace ts { export interface GenericType extends InterfaceType, TypeReference { /* @internal */ instantiations: Map; // Generic instantiation cache + variances?: Variance[]; } export interface UnionOrIntersectionType extends Type { @@ -3440,6 +3441,14 @@ namespace ts { resolvedIndexType: IndexType; } + export const enum Variance { + Invariant = 0, + Covariant = 1, + Contravariant = 2, + Bivariant = Covariant | Contravariant, + Omnivariant = 4 + } + // Type parameters (TypeFlags.TypeParameter) export interface TypeParameter extends TypeVariable { /** Retrieve using getConstraintFromTypeParameter */ @@ -3707,6 +3716,7 @@ namespace ts { sourceMap?: boolean; sourceRoot?: string; strict?: boolean; + strictFunctionTypes?: boolean; // Always combine with strict property strictNullChecks?: boolean; // Always combine with strict property /* @internal */ stripInternal?: boolean; suppressExcessPropertyErrors?: boolean; From f8ff7f73651ac87c50d5a0c6ff16125011be3b6c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 18 Sep 2017 13:36:46 -0700 Subject: [PATCH 105/144] Use dedicated marker types for variance determination --- src/compiler/checker.ts | 16 +++++++++++----- src/compiler/types.ts | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 33d8b9249da..f98e7bde2ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -282,6 +282,10 @@ namespace ts { const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); + const markerSuperType = createType(TypeFlags.MarkerType); + const markerSubType = createType(TypeFlags.MarkerType); + const markerOtherType = createType(TypeFlags.MarkerType); + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); @@ -8944,6 +8948,10 @@ namespace ts { if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; + if (source.flags & TypeFlags.MarkerType && target.flags & TypeFlags.MarkerType) { + return source === markerSubType && target === markerSuperType ? Ternary.True : Ternary.False; + } + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { @@ -9367,8 +9375,6 @@ namespace ts { if (!constraint || constraint.flags & TypeFlags.Any) { constraint = emptyObjectType; } - // The constraint may need to be further instantiated with its 'this' type. - constraint = getTypeWithThisArgument(constraint, source); // Report constraint errors only if the constraint is not the empty object type const reportConstraintErrors = reportErrors && constraint !== emptyObjectType; if (result = isRelatedTo(constraint, target, reportConstraintErrors)) { @@ -9828,11 +9834,11 @@ namespace ts { if (!variances) { variances = type.variances = []; for (const tp of typeParameters) { - const superType = getVarianceType(type, tp, stringType); - const subType = getVarianceType(type, tp, emptyStringType); + const superType = getVarianceType(type, tp, markerSuperType); + const subType = getVarianceType(type, tp, markerSubType); let variance = (isTypeAssignableTo(subType, superType) ? Variance.Covariant : 0) | (isTypeAssignableTo(superType, subType) ? Variance.Contravariant : 0); - if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, numberType), superType)) { + if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), superType)) { variance = Variance.Omnivariant; } variances.push(variance); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cddfcee6413..a837722cc89 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3215,6 +3215,7 @@ namespace ts { NonPrimitive = 1 << 24, // intrinsic object type /* @internal */ JsxAttributes = 1 << 25, // Jsx attributes type + MarkerType = 1 << 26, // Marker type used for variance probing /* @internal */ Nullable = Undefined | Null, From 670d7113dac4624285e305ddb51ba85b72f7236e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 18 Sep 2017 13:56:59 -0700 Subject: [PATCH 106/144] Add quick path for computing array variance as it is already known --- src/compiler/checker.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f98e7bde2ad..65c95344c67 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9832,19 +9832,26 @@ namespace ts { const typeParameters = type.typeParameters || emptyArray; let variances = type.variances; if (!variances) { - variances = type.variances = []; - for (const tp of typeParameters) { - const superType = getVarianceType(type, tp, markerSuperType); - const subType = getVarianceType(type, tp, markerSubType); - let variance = (isTypeAssignableTo(subType, superType) ? Variance.Covariant : 0) | - (isTypeAssignableTo(superType, subType) ? Variance.Contravariant : 0); - if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), superType)) { - variance = Variance.Omnivariant; - } - variances.push(variance); + if (type === globalArrayType || type === globalReadonlyArrayType) { + variances = [Variance.Covariant]; } + else { + type.variances = emptyArray; + variances = []; + for (const tp of typeParameters) { + const superType = getVarianceType(type, tp, markerSuperType); + const subType = getVarianceType(type, tp, markerSubType); + let variance = (isTypeAssignableTo(subType, superType) ? Variance.Covariant : 0) | + (isTypeAssignableTo(superType, subType) ? Variance.Contravariant : 0); + if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), superType)) { + variance = Variance.Omnivariant; + } + variances.push(variance); + } + } + type.variances = variances; } - return variances.length === typeParameters.length ? variances : emptyArray; + return variances; } function isUnconstrainedTypeParameter(type: Type) { From a0fa69ff6c8ffc2ee533407f57ea53d61a28de8c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 18 Sep 2017 17:31:54 -0700 Subject: [PATCH 107/144] Handle contravariance in type inference --- src/compiler/checker.ts | 30 ++++++++++++++++++++++++++---- src/compiler/types.ts | 7 ++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 65c95344c67..ae461119e8b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10129,6 +10129,10 @@ namespace ts { getUnionType(types, /*subtypeReduction*/ true); } + function getCommonSubtype(types: Type[]) { + return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s); + } + function isArrayType(type: Type): boolean { return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalArrayType; } @@ -10655,8 +10659,14 @@ namespace ts { const sourceTypes = (source).typeArguments || emptyArray; const targetTypes = (target).typeArguments || emptyArray; const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + const variances = strictFunctionTypes ? getVariances((source).target) : undefined; for (let i = 0; i < count; i++) { - inferFromTypes(sourceTypes[i], targetTypes[i]); + if (variances && i < variances.length && variances[i] === Variance.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } } } else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { @@ -10727,6 +10737,17 @@ namespace ts { } } + function inferFromContravariantTypes(source: Type, target: Type) { + if (strictFunctionTypes) { + priority ^= InferencePriority.Contravariant; + inferFromTypes(source, target); + priority ^= InferencePriority.Contravariant; + } + else { + inferFromTypes(source, target); + } + } + function getInferenceInfoForType(type: Type) { if (type.flags & TypeFlags.TypeVariable) { for (const inference of inferences) { @@ -10804,7 +10825,7 @@ namespace ts { } function inferFromSignature(source: Signature, target: Signature) { - forEachMatchingParameterType(source, target, inferFromTypes); + forEachMatchingParameterType(source, target, inferFromContravariantTypes); if (source.typePredicate && target.typePredicate && source.typePredicate.kind === target.typePredicate.kind) { inferFromTypes(source.typePredicate.type, target.typePredicate.type); @@ -10879,8 +10900,9 @@ namespace ts { const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates; // Infer widened union or supertype, or the unknown type for no common supertype. We infer union types // for inferences coming from return types in order to avoid common supertype failures. - const unionOrSuperType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? - getUnionType(baseCandidates, /*subtypeReduction*/ true) : getCommonSupertype(baseCandidates); + const unionOrSuperType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) : + context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, /*subtypeReduction*/ true) : + getCommonSupertype(baseCandidates); inferredType = getWidenedType(unionOrSuperType); } else if (context.flags & InferenceFlags.NoDefault) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a837722cc89..67974774ab5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3532,9 +3532,10 @@ namespace ts { } export const enum InferencePriority { - NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type - MappedType = 1 << 1, // Reverse inference for mapped type - ReturnType = 1 << 2, // Inference made from return type of generic function + Contravariant = 1 << 0, // Contravariant inference + NakedTypeVariable = 1 << 1, // Naked type variable in union or intersection type + MappedType = 1 << 2, // Reverse inference for mapped type + ReturnType = 1 << 3, // Inference made from return type of generic function } export interface InferenceInfo { From b58e0fba0ca692a8c1eee06a1967a9f445dcdbdc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Sep 2017 10:11:18 -0700 Subject: [PATCH 108/144] Add comments --- src/compiler/checker.ts | 65 ++++++++++++++++++++++++++++++----------- src/compiler/types.ts | 22 +++++++------- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae461119e8b..8db4466cb86 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9215,7 +9215,11 @@ namespace ts { const length = sources.length <= targets.length ? sources.length : targets.length; let result = Ternary.True; for (let i = 0; i < length; i++) { + // When variance information isn't available we default to covariance. This happens + // in the process of computing variance information for recursive types and when + // comparing 'this' type arguments. const variance = i < variances.length ? variances[i] : Variance.Covariant; + // We simply ignore omnivariant type arguments (because they're never witnessed). if (variance !== Variance.Omnivariant) { const s = sources[i]; const t = targets[i]; @@ -9227,12 +9231,19 @@ namespace ts { related = isRelatedTo(t, s, reportErrors); } else if (variance === Variance.Bivariant) { + // In the bivariant case we first compare contravariantly without reporting + // errors. Then, if that doesn't succeed, we compare covariantly with error + // reporting. Thus, error elaboration will be based on the the covariant check, + // which is generally easier to reason about. related = isRelatedTo(t, s, /*reportErrors*/ false); if (!related) { related = isRelatedTo(s, t, reportErrors); } } else { + // In the invariant case we first compare covariantly, and only when that + // succeeds do we proceed to compare contravariantly. Thus, error elaboration + // will typically be based on the covariant check. related = isRelatedTo(s, t, reportErrors); if (related) { related &= isRelatedTo(t, s, reportErrors); @@ -9404,15 +9415,18 @@ namespace ts { } else { if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { + // We have type references to the same generic type. Obtain the variance information for the + // type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); - if (variances) { - // We have type references to same target type, see if relationship holds for all type arguments - if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { - return result; - } - if (variances !== emptyArray) { - return Ternary.False; - } + if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { + return result; + } + // The type arguments did not relate appropriately, but it may be because getVariances was + // invoked recursively and returned emptyArray (in which case typeArgumentsRelatedTo defaults + // to covariance for all type arguments). In that case we need to contine with a structural + // comparison. Otherwise, we know for certain the instantiations aren't related. + if (variances !== emptyArray) { + return Ternary.False; } } // Even if relationship doesn't hold for unions, intersections, or generic type references, @@ -9828,22 +9842,37 @@ namespace ts { return createTypeReference(type, map(type.typeParameters, t => t === source ? target: t)); } + // Return an array containing the variance of each type parameter. The variance is effectively + // a digest of the type comparisons that occur for each type argument when instantiations of the + // generic type are structurally compared. We infer the variance information by comparing + // instantiations of the generic type for type arguments with known relations. Note that the + // function returns the emptyArray singleton to signal that it has been invoked recursively for + // the given generic type. function getVariances(type: GenericType) { const typeParameters = type.typeParameters || emptyArray; let variances = type.variances; if (!variances) { if (type === globalArrayType || type === globalReadonlyArrayType) { + // Arrays are known to be covariant, no need to spend time computing this variances = [Variance.Covariant]; } else { + // The emptyArray singleton is used to signal a recursive invocation. type.variances = emptyArray; variances = []; for (const tp of typeParameters) { - const superType = getVarianceType(type, tp, markerSuperType); - const subType = getVarianceType(type, tp, markerSubType); - let variance = (isTypeAssignableTo(subType, superType) ? Variance.Covariant : 0) | - (isTypeAssignableTo(superType, subType) ? Variance.Contravariant : 0); - if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), superType)) { + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. + const typeWithSuper = getVarianceType(type, tp, markerSuperType); + const typeWithSub = getVarianceType(type, tp, markerSubType); + let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) | + (isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0); + // If the instantiations appear to be related bivariantly, it may be because the + // type parameter is omnivariant (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), typeWithSuper)) { variance = Variance.Omnivariant; } variances.push(variance); @@ -10129,6 +10158,7 @@ namespace ts { getUnionType(types, /*subtypeReduction*/ true); } + // Return the leftmost type for which no type to the right is a subtype. function getCommonSubtype(types: Type[]) { return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s); } @@ -10898,12 +10928,13 @@ namespace ts { !hasPrimitiveConstraint(inference.typeParameter) && (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); const baseCandidates = widenLiteralTypes ? sameMap(inference.candidates, getWidenedLiteralType) : inference.candidates; - // Infer widened union or supertype, or the unknown type for no common supertype. We infer union types - // for inferences coming from return types in order to avoid common supertype failures. - const unionOrSuperType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) : + // If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if + // union types were requested or if all inferences were made from the return type position, infer a + // union type. Otherwise, infer a common supertype. + const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) : context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, /*subtypeReduction*/ true) : getCommonSupertype(baseCandidates); - inferredType = getWidenedType(unionOrSuperType); + inferredType = getWidenedType(unwidenedType); } else if (context.flags & InferenceFlags.NoDefault) { // We use silentNeverType as the wildcard that signals no inferences. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 67974774ab5..3f07716fd1b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3344,11 +3344,19 @@ namespace ts { typeArguments?: Type[]; // Type reference type arguments (undefined if none) } + export const enum Variance { + Invariant = 0, // Both covariant and contravariant + Covariant = 1, // Covariant + Contravariant = 2, // Contravariant + Bivariant = 3, // Either covariant or contravariant + Omnivariant = 4 // Unwitnessed type parameter + } + // Generic class and interface types export interface GenericType extends InterfaceType, TypeReference { /* @internal */ - instantiations: Map; // Generic instantiation cache - variances?: Variance[]; + instantiations: Map; // Generic instantiation cache + variances?: Variance[]; // Variance of each type parameter } export interface UnionOrIntersectionType extends Type { @@ -3442,14 +3450,6 @@ namespace ts { resolvedIndexType: IndexType; } - export const enum Variance { - Invariant = 0, - Covariant = 1, - Contravariant = 2, - Bivariant = Covariant | Contravariant, - Omnivariant = 4 - } - // Type parameters (TypeFlags.TypeParameter) export interface TypeParameter extends TypeVariable { /** Retrieve using getConstraintFromTypeParameter */ @@ -3532,7 +3532,7 @@ namespace ts { } export const enum InferencePriority { - Contravariant = 1 << 0, // Contravariant inference + Contravariant = 1 << 0, // Inference from contravariant position NakedTypeVariable = 1 << 1, // Naked type variable in union or intersection type MappedType = 1 << 2, // Reverse inference for mapped type ReturnType = 1 << 3, // Inference made from return type of generic function From 84f7afd29e3082d5279594aee745e68f958ebc88 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Sep 2017 13:29:03 -0700 Subject: [PATCH 109/144] Handle special case of 'void' type arguments for covariant type parameters --- src/compiler/checker.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8db4466cb86..4bacb88ef4a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9422,10 +9422,13 @@ namespace ts { return result; } // The type arguments did not relate appropriately, but it may be because getVariances was - // invoked recursively and returned emptyArray (in which case typeArgumentsRelatedTo defaults - // to covariance for all type arguments). In that case we need to contine with a structural - // comparison. Otherwise, we know for certain the instantiations aren't related. - if (variances !== emptyArray) { + // invoked recursively and returned emptyArray (in which case typeArgumentsRelatedTo defaulted + // to covariance for all type arguments). It might also be the case that the target type has a + // 'void' type argument for a covariant type parameter that is only used in return positions + // within the generic type (in which case any type argument is permitted on the source side). + // In those cases we proceed with a structural comparison. Otherwise, we know for certain the + // instantiations aren't related and we can return here. + if (variances !== emptyArray && !hasCovariantVoidArgument(target, variances)) { return Ternary.False; } } @@ -9839,7 +9842,7 @@ namespace ts { } function getVarianceType(type: GenericType, source: TypeParameter, target: Type) { - return createTypeReference(type, map(type.typeParameters, t => t === source ? target: t)); + return createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); } // Return an array containing the variance of each type parameter. The variance is effectively @@ -9848,7 +9851,7 @@ namespace ts { // instantiations of the generic type for type arguments with known relations. Note that the // function returns the emptyArray singleton to signal that it has been invoked recursively for // the given generic type. - function getVariances(type: GenericType) { + function getVariances(type: GenericType): Variance[] { const typeParameters = type.typeParameters || emptyArray; let variances = type.variances; if (!variances) { @@ -9883,6 +9886,17 @@ namespace ts { return variances; } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. + // See comment at call in recursiveTypeRelatedTo for when this case matters. + function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean { + for (let i = 0; i < variances.length; i++) { + if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) { + return true; + } + } + return false; + } + function isUnconstrainedTypeParameter(type: Type) { return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(type); } From 54eadef408884239025aa70c16d933e0c285d924 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Sep 2017 13:30:18 -0700 Subject: [PATCH 110/144] Accept new baselines --- tests/baselines/reference/fuzzy.errors.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/baselines/reference/fuzzy.errors.txt b/tests/baselines/reference/fuzzy.errors.txt index c3f05fd7f2f..b32f225a33e 100644 --- a/tests/baselines/reference/fuzzy.errors.txt +++ b/tests/baselines/reference/fuzzy.errors.txt @@ -4,6 +4,7 @@ tests/cases/compiler/fuzzy.ts(21,13): error TS2322: Type '{ anything: number; on Types of property 'oneI' are incompatible. Type 'this' is not assignable to type 'I'. Type 'C' is not assignable to type 'I'. + Property 'alsoWorks' is missing in type 'C'. tests/cases/compiler/fuzzy.ts(25,20): error TS2352: Type '{ oneI: this; }' cannot be converted to type 'R'. Property 'anything' is missing in type '{ oneI: this; }'. @@ -38,6 +39,7 @@ tests/cases/compiler/fuzzy.ts(25,20): error TS2352: Type '{ oneI: this; }' canno !!! error TS2322: Types of property 'oneI' are incompatible. !!! error TS2322: Type 'this' is not assignable to type 'I'. !!! error TS2322: Type 'C' is not assignable to type 'I'. +!!! error TS2322: Property 'alsoWorks' is missing in type 'C'. } worksToo():R { From 44cc8c5ffe53d0fd034275fe4f6ebe809d58db43 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Sep 2017 14:23:01 -0700 Subject: [PATCH 111/144] Use methods in dom.generated.d.ts to opt out of strict checks --- src/lib/dom.generated.d.ts | 214 ++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index d4ee81e4974..93bfb982955 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -4230,25 +4230,25 @@ interface HTMLBodyElement extends HTMLElement { bgProperties: string; link: any; noWrap: boolean; - onafterprint: (this: HTMLBodyElement, ev: Event) => any; - onbeforeprint: (this: HTMLBodyElement, ev: Event) => any; - onbeforeunload: (this: HTMLBodyElement, ev: BeforeUnloadEvent) => any; - onblur: (this: HTMLBodyElement, ev: FocusEvent) => any; - onerror: (this: HTMLBodyElement, ev: ErrorEvent) => any; - onfocus: (this: HTMLBodyElement, ev: FocusEvent) => any; - onhashchange: (this: HTMLBodyElement, ev: HashChangeEvent) => any; - onload: (this: HTMLBodyElement, ev: Event) => any; - onmessage: (this: HTMLBodyElement, ev: MessageEvent) => any; - onoffline: (this: HTMLBodyElement, ev: Event) => any; - ononline: (this: HTMLBodyElement, ev: Event) => any; - onorientationchange: (this: HTMLBodyElement, ev: Event) => any; - onpagehide: (this: HTMLBodyElement, ev: PageTransitionEvent) => any; - onpageshow: (this: HTMLBodyElement, ev: PageTransitionEvent) => any; - onpopstate: (this: HTMLBodyElement, ev: PopStateEvent) => any; - onresize: (this: HTMLBodyElement, ev: UIEvent) => any; - onscroll: (this: HTMLBodyElement, ev: UIEvent) => any; - onstorage: (this: HTMLBodyElement, ev: StorageEvent) => any; - onunload: (this: HTMLBodyElement, ev: Event) => any; + onafterprint(this: HTMLBodyElement, ev: Event): any; + onbeforeprint(this: HTMLBodyElement, ev: Event): any; + onbeforeunload(this: HTMLBodyElement, ev: BeforeUnloadEvent): any; + onblur(this: HTMLBodyElement, ev: FocusEvent): any; + onerror(this: HTMLBodyElement, ev: ErrorEvent): any; + onfocus(this: HTMLBodyElement, ev: FocusEvent): any; + onhashchange(this: HTMLBodyElement, ev: HashChangeEvent): any; + onload(this: HTMLBodyElement, ev: Event): any; + onmessage(this: HTMLBodyElement, ev: MessageEvent): any; + onoffline(this: HTMLBodyElement, ev: Event): any; + ononline(this: HTMLBodyElement, ev: Event): any; + onorientationchange(this: HTMLBodyElement, ev: Event): any; + onpagehide(this: HTMLBodyElement, ev: PageTransitionEvent): any; + onpageshow(this: HTMLBodyElement, ev: PageTransitionEvent): any; + onpopstate(this: HTMLBodyElement, ev: PopStateEvent): any; + onresize(this: HTMLBodyElement, ev: UIEvent): any; + onscroll(this: HTMLBodyElement, ev: UIEvent): any; + onstorage(this: HTMLBodyElement, ev: StorageEvent): any; + onunload(this: HTMLBodyElement, ev: Event): any; text: any; vLink: any; addEventListener(type: K, listener: (this: HTMLBodyElement, ev: HTMLBodyElementEventMap[K]) => any, useCapture?: boolean): void; @@ -4565,73 +4565,73 @@ interface HTMLElement extends Element { readonly offsetParent: Element; readonly offsetTop: number; readonly offsetWidth: number; - onabort: (this: HTMLElement, ev: UIEvent) => any; - onactivate: (this: HTMLElement, ev: UIEvent) => any; - onbeforeactivate: (this: HTMLElement, ev: UIEvent) => any; - onbeforecopy: (this: HTMLElement, ev: ClipboardEvent) => any; - onbeforecut: (this: HTMLElement, ev: ClipboardEvent) => any; - onbeforedeactivate: (this: HTMLElement, ev: UIEvent) => any; - onbeforepaste: (this: HTMLElement, ev: ClipboardEvent) => any; - onblur: (this: HTMLElement, ev: FocusEvent) => any; - oncanplay: (this: HTMLElement, ev: Event) => any; - oncanplaythrough: (this: HTMLElement, ev: Event) => any; - onchange: (this: HTMLElement, ev: Event) => any; - onclick: (this: HTMLElement, ev: MouseEvent) => any; - oncontextmenu: (this: HTMLElement, ev: PointerEvent) => any; - oncopy: (this: HTMLElement, ev: ClipboardEvent) => any; - oncuechange: (this: HTMLElement, ev: Event) => any; - oncut: (this: HTMLElement, ev: ClipboardEvent) => any; - ondblclick: (this: HTMLElement, ev: MouseEvent) => any; - ondeactivate: (this: HTMLElement, ev: UIEvent) => any; - ondrag: (this: HTMLElement, ev: DragEvent) => any; - ondragend: (this: HTMLElement, ev: DragEvent) => any; - ondragenter: (this: HTMLElement, ev: DragEvent) => any; - ondragleave: (this: HTMLElement, ev: DragEvent) => any; - ondragover: (this: HTMLElement, ev: DragEvent) => any; - ondragstart: (this: HTMLElement, ev: DragEvent) => any; - ondrop: (this: HTMLElement, ev: DragEvent) => any; - ondurationchange: (this: HTMLElement, ev: Event) => any; - onemptied: (this: HTMLElement, ev: Event) => any; - onended: (this: HTMLElement, ev: MediaStreamErrorEvent) => any; - onerror: (this: HTMLElement, ev: ErrorEvent) => any; - onfocus: (this: HTMLElement, ev: FocusEvent) => any; - oninput: (this: HTMLElement, ev: Event) => any; - oninvalid: (this: HTMLElement, ev: Event) => any; - onkeydown: (this: HTMLElement, ev: KeyboardEvent) => any; - onkeypress: (this: HTMLElement, ev: KeyboardEvent) => any; - onkeyup: (this: HTMLElement, ev: KeyboardEvent) => any; - onload: (this: HTMLElement, ev: Event) => any; - onloadeddata: (this: HTMLElement, ev: Event) => any; - onloadedmetadata: (this: HTMLElement, ev: Event) => any; - onloadstart: (this: HTMLElement, ev: Event) => any; - onmousedown: (this: HTMLElement, ev: MouseEvent) => any; - onmouseenter: (this: HTMLElement, ev: MouseEvent) => any; - onmouseleave: (this: HTMLElement, ev: MouseEvent) => any; - onmousemove: (this: HTMLElement, ev: MouseEvent) => any; - onmouseout: (this: HTMLElement, ev: MouseEvent) => any; - onmouseover: (this: HTMLElement, ev: MouseEvent) => any; - onmouseup: (this: HTMLElement, ev: MouseEvent) => any; - onmousewheel: (this: HTMLElement, ev: WheelEvent) => any; - onmscontentzoom: (this: HTMLElement, ev: UIEvent) => any; - onmsmanipulationstatechanged: (this: HTMLElement, ev: MSManipulationEvent) => any; - onpaste: (this: HTMLElement, ev: ClipboardEvent) => any; - onpause: (this: HTMLElement, ev: Event) => any; - onplay: (this: HTMLElement, ev: Event) => any; - onplaying: (this: HTMLElement, ev: Event) => any; - onprogress: (this: HTMLElement, ev: ProgressEvent) => any; - onratechange: (this: HTMLElement, ev: Event) => any; - onreset: (this: HTMLElement, ev: Event) => any; - onscroll: (this: HTMLElement, ev: UIEvent) => any; - onseeked: (this: HTMLElement, ev: Event) => any; - onseeking: (this: HTMLElement, ev: Event) => any; - onselect: (this: HTMLElement, ev: UIEvent) => any; - onselectstart: (this: HTMLElement, ev: Event) => any; - onstalled: (this: HTMLElement, ev: Event) => any; - onsubmit: (this: HTMLElement, ev: Event) => any; - onsuspend: (this: HTMLElement, ev: Event) => any; - ontimeupdate: (this: HTMLElement, ev: Event) => any; - onvolumechange: (this: HTMLElement, ev: Event) => any; - onwaiting: (this: HTMLElement, ev: Event) => any; + onabort(this: HTMLElement, ev: UIEvent): any; + onactivate(this: HTMLElement, ev: UIEvent): any; + onbeforeactivate(this: HTMLElement, ev: UIEvent): any; + onbeforecopy(this: HTMLElement, ev: ClipboardEvent): any; + onbeforecut(this: HTMLElement, ev: ClipboardEvent): any; + onbeforedeactivate(this: HTMLElement, ev: UIEvent): any; + onbeforepaste(this: HTMLElement, ev: ClipboardEvent): any; + onblur(this: HTMLElement, ev: FocusEvent): any; + oncanplay(this: HTMLElement, ev: Event): any; + oncanplaythrough(this: HTMLElement, ev: Event): any; + onchange(this: HTMLElement, ev: Event): any; + onclick(this: HTMLElement, ev: MouseEvent): any; + oncontextmenu(this: HTMLElement, ev: PointerEvent): any; + oncopy(this: HTMLElement, ev: ClipboardEvent): any; + oncuechange(this: HTMLElement, ev: Event): any; + oncut(this: HTMLElement, ev: ClipboardEvent): any; + ondblclick(this: HTMLElement, ev: MouseEvent): any; + ondeactivate(this: HTMLElement, ev: UIEvent): any; + ondrag(this: HTMLElement, ev: DragEvent): any; + ondragend(this: HTMLElement, ev: DragEvent): any; + ondragenter(this: HTMLElement, ev: DragEvent): any; + ondragleave(this: HTMLElement, ev: DragEvent): any; + ondragover(this: HTMLElement, ev: DragEvent): any; + ondragstart(this: HTMLElement, ev: DragEvent): any; + ondrop(this: HTMLElement, ev: DragEvent): any; + ondurationchange(this: HTMLElement, ev: Event): any; + onemptied(this: HTMLElement, ev: Event): any; + onended(this: HTMLElement, ev: MediaStreamErrorEvent): any; + onerror(this: HTMLElement, ev: ErrorEvent): any; + onfocus(this: HTMLElement, ev: FocusEvent): any; + oninput(this: HTMLElement, ev: Event): any; + oninvalid(this: HTMLElement, ev: Event): any; + onkeydown(this: HTMLElement, ev: KeyboardEvent): any; + onkeypress(this: HTMLElement, ev: KeyboardEvent): any; + onkeyup(this: HTMLElement, ev: KeyboardEvent): any; + onload(this: HTMLElement, ev: Event): any; + onloadeddata(this: HTMLElement, ev: Event): any; + onloadedmetadata(this: HTMLElement, ev: Event): any; + onloadstart(this: HTMLElement, ev: Event): any; + onmousedown(this: HTMLElement, ev: MouseEvent): any; + onmouseenter(this: HTMLElement, ev: MouseEvent): any; + onmouseleave(this: HTMLElement, ev: MouseEvent): any; + onmousemove(this: HTMLElement, ev: MouseEvent): any; + onmouseout(this: HTMLElement, ev: MouseEvent): any; + onmouseover(this: HTMLElement, ev: MouseEvent): any; + onmouseup(this: HTMLElement, ev: MouseEvent): any; + onmousewheel(this: HTMLElement, ev: WheelEvent): any; + onmscontentzoom(this: HTMLElement, ev: UIEvent): any; + onmsmanipulationstatechanged(this: HTMLElement, ev: MSManipulationEvent): any; + onpaste(this: HTMLElement, ev: ClipboardEvent): any; + onpause(this: HTMLElement, ev: Event): any; + onplay(this: HTMLElement, ev: Event): any; + onplaying(this: HTMLElement, ev: Event): any; + onprogress(this: HTMLElement, ev: ProgressEvent): any; + onratechange(this: HTMLElement, ev: Event): any; + onreset(this: HTMLElement, ev: Event): any; + onscroll(this: HTMLElement, ev: UIEvent): any; + onseeked(this: HTMLElement, ev: Event): any; + onseeking(this: HTMLElement, ev: Event): any; + onselect(this: HTMLElement, ev: UIEvent): any; + onselectstart(this: HTMLElement, ev: Event): any; + onstalled(this: HTMLElement, ev: Event): any; + onsubmit(this: HTMLElement, ev: Event): any; + onsuspend(this: HTMLElement, ev: Event): any; + ontimeupdate(this: HTMLElement, ev: Event): any; + onvolumechange(this: HTMLElement, ev: Event): any; + onwaiting(this: HTMLElement, ev: Event): any; outerText: string; spellcheck: boolean; readonly style: CSSStyleDeclaration; @@ -4904,7 +4904,7 @@ interface HTMLFrameElement extends HTMLElement, GetSVGDocument { /** * Raised when the object has been completely received from the server. */ - onload: (this: HTMLFrameElement, ev: Event) => any; + onload(this: HTMLFrameElement, ev: Event): any; /** * Sets or retrieves whether the frame can be scrolled. */ @@ -4967,31 +4967,31 @@ interface HTMLFrameSetElement extends HTMLElement { */ frameSpacing: any; name: string; - onafterprint: (this: HTMLFrameSetElement, ev: Event) => any; - onbeforeprint: (this: HTMLFrameSetElement, ev: Event) => any; - onbeforeunload: (this: HTMLFrameSetElement, ev: BeforeUnloadEvent) => any; + onafterprint(this: HTMLFrameSetElement, ev: Event): any; + onbeforeprint(this: HTMLFrameSetElement, ev: Event): any; + onbeforeunload(this: HTMLFrameSetElement, ev: BeforeUnloadEvent): any; /** * Fires when the object loses the input focus. */ - onblur: (this: HTMLFrameSetElement, ev: FocusEvent) => any; - onerror: (this: HTMLFrameSetElement, ev: ErrorEvent) => any; + onblur(this: HTMLFrameSetElement, ev: FocusEvent): any; + onerror(this: HTMLFrameSetElement, ev: ErrorEvent): any; /** * Fires when the object receives focus. */ - onfocus: (this: HTMLFrameSetElement, ev: FocusEvent) => any; - onhashchange: (this: HTMLFrameSetElement, ev: HashChangeEvent) => any; - onload: (this: HTMLFrameSetElement, ev: Event) => any; - onmessage: (this: HTMLFrameSetElement, ev: MessageEvent) => any; - onoffline: (this: HTMLFrameSetElement, ev: Event) => any; - ononline: (this: HTMLFrameSetElement, ev: Event) => any; - onorientationchange: (this: HTMLFrameSetElement, ev: Event) => any; - onpagehide: (this: HTMLFrameSetElement, ev: PageTransitionEvent) => any; - onpageshow: (this: HTMLFrameSetElement, ev: PageTransitionEvent) => any; - onpopstate: (this: HTMLFrameSetElement, ev: PopStateEvent) => any; - onresize: (this: HTMLFrameSetElement, ev: UIEvent) => any; - onscroll: (this: HTMLFrameSetElement, ev: UIEvent) => any; - onstorage: (this: HTMLFrameSetElement, ev: StorageEvent) => any; - onunload: (this: HTMLFrameSetElement, ev: Event) => any; + onfocus(this: HTMLFrameSetElement, ev: FocusEvent): any; + onhashchange(this: HTMLFrameSetElement, ev: HashChangeEvent): any; + onload(this: HTMLFrameSetElement, ev: Event): any; + onmessage(this: HTMLFrameSetElement, ev: MessageEvent): any; + onoffline(this: HTMLFrameSetElement, ev: Event): any; + ononline(this: HTMLFrameSetElement, ev: Event): any; + onorientationchange(this: HTMLFrameSetElement, ev: Event): any; + onpagehide(this: HTMLFrameSetElement, ev: PageTransitionEvent): any; + onpageshow(this: HTMLFrameSetElement, ev: PageTransitionEvent): any; + onpopstate(this: HTMLFrameSetElement, ev: PopStateEvent): any; + onresize(this: HTMLFrameSetElement, ev: UIEvent): any; + onscroll(this: HTMLFrameSetElement, ev: UIEvent): any; + onstorage(this: HTMLFrameSetElement, ev: StorageEvent): any; + onunload(this: HTMLFrameSetElement, ev: Event): any; /** * Sets or retrieves the frame heights of the object. */ @@ -5128,7 +5128,7 @@ interface HTMLIFrameElement extends HTMLElement, GetSVGDocument { /** * Raised when the object has been completely received from the server. */ - onload: (this: HTMLIFrameElement, ev: Event) => any; + onload(this: HTMLIFrameElement, ev: Event): any; readonly sandbox: DOMSettableTokenList; /** * Sets or retrieves whether the frame can be scrolled. From dd466ae599587d863c678debf44129d618467738 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Sep 2017 15:29:36 -0700 Subject: [PATCH 112/144] Update tsconfig baselines --- .../tsConfig/Default initialized TSConfig/tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../Initialized TSConfig with files options/tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + .../tsconfig.json | 1 + 8 files changed, 8 insertions(+) diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 0f5b2378468..bae0e1c0eb0 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index a545124a723..4d0db39092f 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index b53ac2d8552..010a1186211 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 4e06e06d159..b78057d0708 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 94808d89ed0..be4fc190f9b 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 0f5b2378468..bae0e1c0eb0 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index d165b0f2775..d7496d18dbb 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -22,6 +22,7 @@ "strict": true /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index 2a169b3aaaf..6d6964b8294 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -22,6 +22,7 @@ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ From 24698dd353e9303ed311705630bfcd8bf75ad88a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 20 Sep 2017 13:49:57 -0700 Subject: [PATCH 113/144] Revert dom.generated.d.ts and fix duplicate declarations --- src/lib/dom.generated.d.ts | 212 +++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 116 deletions(-) diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index 93bfb982955..420be2f0f5d 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -4230,25 +4230,20 @@ interface HTMLBodyElement extends HTMLElement { bgProperties: string; link: any; noWrap: boolean; - onafterprint(this: HTMLBodyElement, ev: Event): any; - onbeforeprint(this: HTMLBodyElement, ev: Event): any; - onbeforeunload(this: HTMLBodyElement, ev: BeforeUnloadEvent): any; - onblur(this: HTMLBodyElement, ev: FocusEvent): any; - onerror(this: HTMLBodyElement, ev: ErrorEvent): any; - onfocus(this: HTMLBodyElement, ev: FocusEvent): any; - onhashchange(this: HTMLBodyElement, ev: HashChangeEvent): any; - onload(this: HTMLBodyElement, ev: Event): any; - onmessage(this: HTMLBodyElement, ev: MessageEvent): any; - onoffline(this: HTMLBodyElement, ev: Event): any; - ononline(this: HTMLBodyElement, ev: Event): any; - onorientationchange(this: HTMLBodyElement, ev: Event): any; - onpagehide(this: HTMLBodyElement, ev: PageTransitionEvent): any; - onpageshow(this: HTMLBodyElement, ev: PageTransitionEvent): any; - onpopstate(this: HTMLBodyElement, ev: PopStateEvent): any; - onresize(this: HTMLBodyElement, ev: UIEvent): any; - onscroll(this: HTMLBodyElement, ev: UIEvent): any; - onstorage(this: HTMLBodyElement, ev: StorageEvent): any; - onunload(this: HTMLBodyElement, ev: Event): any; + onafterprint: (this: HTMLBodyElement, ev: Event) => any; + onbeforeprint: (this: HTMLBodyElement, ev: Event) => any; + onbeforeunload: (this: HTMLBodyElement, ev: BeforeUnloadEvent) => any; + onhashchange: (this: HTMLBodyElement, ev: HashChangeEvent) => any; + onmessage: (this: HTMLBodyElement, ev: MessageEvent) => any; + onoffline: (this: HTMLBodyElement, ev: Event) => any; + ononline: (this: HTMLBodyElement, ev: Event) => any; + onorientationchange: (this: HTMLBodyElement, ev: Event) => any; + onpagehide: (this: HTMLBodyElement, ev: PageTransitionEvent) => any; + onpageshow: (this: HTMLBodyElement, ev: PageTransitionEvent) => any; + onpopstate: (this: HTMLBodyElement, ev: PopStateEvent) => any; + onresize: (this: HTMLBodyElement, ev: UIEvent) => any; + onstorage: (this: HTMLBodyElement, ev: StorageEvent) => any; + onunload: (this: HTMLBodyElement, ev: Event) => any; text: any; vLink: any; addEventListener(type: K, listener: (this: HTMLBodyElement, ev: HTMLBodyElementEventMap[K]) => any, useCapture?: boolean): void; @@ -4565,73 +4560,73 @@ interface HTMLElement extends Element { readonly offsetParent: Element; readonly offsetTop: number; readonly offsetWidth: number; - onabort(this: HTMLElement, ev: UIEvent): any; - onactivate(this: HTMLElement, ev: UIEvent): any; - onbeforeactivate(this: HTMLElement, ev: UIEvent): any; - onbeforecopy(this: HTMLElement, ev: ClipboardEvent): any; - onbeforecut(this: HTMLElement, ev: ClipboardEvent): any; - onbeforedeactivate(this: HTMLElement, ev: UIEvent): any; - onbeforepaste(this: HTMLElement, ev: ClipboardEvent): any; - onblur(this: HTMLElement, ev: FocusEvent): any; - oncanplay(this: HTMLElement, ev: Event): any; - oncanplaythrough(this: HTMLElement, ev: Event): any; - onchange(this: HTMLElement, ev: Event): any; - onclick(this: HTMLElement, ev: MouseEvent): any; - oncontextmenu(this: HTMLElement, ev: PointerEvent): any; - oncopy(this: HTMLElement, ev: ClipboardEvent): any; - oncuechange(this: HTMLElement, ev: Event): any; - oncut(this: HTMLElement, ev: ClipboardEvent): any; - ondblclick(this: HTMLElement, ev: MouseEvent): any; - ondeactivate(this: HTMLElement, ev: UIEvent): any; - ondrag(this: HTMLElement, ev: DragEvent): any; - ondragend(this: HTMLElement, ev: DragEvent): any; - ondragenter(this: HTMLElement, ev: DragEvent): any; - ondragleave(this: HTMLElement, ev: DragEvent): any; - ondragover(this: HTMLElement, ev: DragEvent): any; - ondragstart(this: HTMLElement, ev: DragEvent): any; - ondrop(this: HTMLElement, ev: DragEvent): any; - ondurationchange(this: HTMLElement, ev: Event): any; - onemptied(this: HTMLElement, ev: Event): any; - onended(this: HTMLElement, ev: MediaStreamErrorEvent): any; - onerror(this: HTMLElement, ev: ErrorEvent): any; - onfocus(this: HTMLElement, ev: FocusEvent): any; - oninput(this: HTMLElement, ev: Event): any; - oninvalid(this: HTMLElement, ev: Event): any; - onkeydown(this: HTMLElement, ev: KeyboardEvent): any; - onkeypress(this: HTMLElement, ev: KeyboardEvent): any; - onkeyup(this: HTMLElement, ev: KeyboardEvent): any; - onload(this: HTMLElement, ev: Event): any; - onloadeddata(this: HTMLElement, ev: Event): any; - onloadedmetadata(this: HTMLElement, ev: Event): any; - onloadstart(this: HTMLElement, ev: Event): any; - onmousedown(this: HTMLElement, ev: MouseEvent): any; - onmouseenter(this: HTMLElement, ev: MouseEvent): any; - onmouseleave(this: HTMLElement, ev: MouseEvent): any; - onmousemove(this: HTMLElement, ev: MouseEvent): any; - onmouseout(this: HTMLElement, ev: MouseEvent): any; - onmouseover(this: HTMLElement, ev: MouseEvent): any; - onmouseup(this: HTMLElement, ev: MouseEvent): any; - onmousewheel(this: HTMLElement, ev: WheelEvent): any; - onmscontentzoom(this: HTMLElement, ev: UIEvent): any; - onmsmanipulationstatechanged(this: HTMLElement, ev: MSManipulationEvent): any; - onpaste(this: HTMLElement, ev: ClipboardEvent): any; - onpause(this: HTMLElement, ev: Event): any; - onplay(this: HTMLElement, ev: Event): any; - onplaying(this: HTMLElement, ev: Event): any; - onprogress(this: HTMLElement, ev: ProgressEvent): any; - onratechange(this: HTMLElement, ev: Event): any; - onreset(this: HTMLElement, ev: Event): any; - onscroll(this: HTMLElement, ev: UIEvent): any; - onseeked(this: HTMLElement, ev: Event): any; - onseeking(this: HTMLElement, ev: Event): any; - onselect(this: HTMLElement, ev: UIEvent): any; - onselectstart(this: HTMLElement, ev: Event): any; - onstalled(this: HTMLElement, ev: Event): any; - onsubmit(this: HTMLElement, ev: Event): any; - onsuspend(this: HTMLElement, ev: Event): any; - ontimeupdate(this: HTMLElement, ev: Event): any; - onvolumechange(this: HTMLElement, ev: Event): any; - onwaiting(this: HTMLElement, ev: Event): any; + onabort: (this: HTMLElement, ev: UIEvent) => any; + onactivate: (this: HTMLElement, ev: UIEvent) => any; + onbeforeactivate: (this: HTMLElement, ev: UIEvent) => any; + onbeforecopy: (this: HTMLElement, ev: ClipboardEvent) => any; + onbeforecut: (this: HTMLElement, ev: ClipboardEvent) => any; + onbeforedeactivate: (this: HTMLElement, ev: UIEvent) => any; + onbeforepaste: (this: HTMLElement, ev: ClipboardEvent) => any; + onblur: (this: HTMLElement, ev: FocusEvent) => any; + oncanplay: (this: HTMLElement, ev: Event) => any; + oncanplaythrough: (this: HTMLElement, ev: Event) => any; + onchange: (this: HTMLElement, ev: Event) => any; + onclick: (this: HTMLElement, ev: MouseEvent) => any; + oncontextmenu: (this: HTMLElement, ev: PointerEvent) => any; + oncopy: (this: HTMLElement, ev: ClipboardEvent) => any; + oncuechange: (this: HTMLElement, ev: Event) => any; + oncut: (this: HTMLElement, ev: ClipboardEvent) => any; + ondblclick: (this: HTMLElement, ev: MouseEvent) => any; + ondeactivate: (this: HTMLElement, ev: UIEvent) => any; + ondrag: (this: HTMLElement, ev: DragEvent) => any; + ondragend: (this: HTMLElement, ev: DragEvent) => any; + ondragenter: (this: HTMLElement, ev: DragEvent) => any; + ondragleave: (this: HTMLElement, ev: DragEvent) => any; + ondragover: (this: HTMLElement, ev: DragEvent) => any; + ondragstart: (this: HTMLElement, ev: DragEvent) => any; + ondrop: (this: HTMLElement, ev: DragEvent) => any; + ondurationchange: (this: HTMLElement, ev: Event) => any; + onemptied: (this: HTMLElement, ev: Event) => any; + onended: (this: HTMLElement, ev: MediaStreamErrorEvent) => any; + onerror: (this: HTMLElement, ev: ErrorEvent) => any; + onfocus: (this: HTMLElement, ev: FocusEvent) => any; + oninput: (this: HTMLElement, ev: Event) => any; + oninvalid: (this: HTMLElement, ev: Event) => any; + onkeydown: (this: HTMLElement, ev: KeyboardEvent) => any; + onkeypress: (this: HTMLElement, ev: KeyboardEvent) => any; + onkeyup: (this: HTMLElement, ev: KeyboardEvent) => any; + onload: (this: HTMLElement, ev: Event) => any; + onloadeddata: (this: HTMLElement, ev: Event) => any; + onloadedmetadata: (this: HTMLElement, ev: Event) => any; + onloadstart: (this: HTMLElement, ev: Event) => any; + onmousedown: (this: HTMLElement, ev: MouseEvent) => any; + onmouseenter: (this: HTMLElement, ev: MouseEvent) => any; + onmouseleave: (this: HTMLElement, ev: MouseEvent) => any; + onmousemove: (this: HTMLElement, ev: MouseEvent) => any; + onmouseout: (this: HTMLElement, ev: MouseEvent) => any; + onmouseover: (this: HTMLElement, ev: MouseEvent) => any; + onmouseup: (this: HTMLElement, ev: MouseEvent) => any; + onmousewheel: (this: HTMLElement, ev: WheelEvent) => any; + onmscontentzoom: (this: HTMLElement, ev: UIEvent) => any; + onmsmanipulationstatechanged: (this: HTMLElement, ev: MSManipulationEvent) => any; + onpaste: (this: HTMLElement, ev: ClipboardEvent) => any; + onpause: (this: HTMLElement, ev: Event) => any; + onplay: (this: HTMLElement, ev: Event) => any; + onplaying: (this: HTMLElement, ev: Event) => any; + onprogress: (this: HTMLElement, ev: ProgressEvent) => any; + onratechange: (this: HTMLElement, ev: Event) => any; + onreset: (this: HTMLElement, ev: Event) => any; + onscroll: (this: HTMLElement, ev: UIEvent) => any; + onseeked: (this: HTMLElement, ev: Event) => any; + onseeking: (this: HTMLElement, ev: Event) => any; + onselect: (this: HTMLElement, ev: UIEvent) => any; + onselectstart: (this: HTMLElement, ev: Event) => any; + onstalled: (this: HTMLElement, ev: Event) => any; + onsubmit: (this: HTMLElement, ev: Event) => any; + onsuspend: (this: HTMLElement, ev: Event) => any; + ontimeupdate: (this: HTMLElement, ev: Event) => any; + onvolumechange: (this: HTMLElement, ev: Event) => any; + onwaiting: (this: HTMLElement, ev: Event) => any; outerText: string; spellcheck: boolean; readonly style: CSSStyleDeclaration; @@ -4901,10 +4896,6 @@ interface HTMLFrameElement extends HTMLElement, GetSVGDocument { * Sets or retrieves whether the user can resize the frame. */ noResize: boolean; - /** - * Raised when the object has been completely received from the server. - */ - onload(this: HTMLFrameElement, ev: Event): any; /** * Sets or retrieves whether the frame can be scrolled. */ @@ -4967,31 +4958,23 @@ interface HTMLFrameSetElement extends HTMLElement { */ frameSpacing: any; name: string; - onafterprint(this: HTMLFrameSetElement, ev: Event): any; - onbeforeprint(this: HTMLFrameSetElement, ev: Event): any; - onbeforeunload(this: HTMLFrameSetElement, ev: BeforeUnloadEvent): any; - /** - * Fires when the object loses the input focus. - */ - onblur(this: HTMLFrameSetElement, ev: FocusEvent): any; - onerror(this: HTMLFrameSetElement, ev: ErrorEvent): any; + onafterprint: (this: HTMLFrameSetElement, ev: Event) => any; + onbeforeprint: (this: HTMLFrameSetElement, ev: Event) => any; + onbeforeunload: (this: HTMLFrameSetElement, ev: BeforeUnloadEvent) => any; /** * Fires when the object receives focus. */ - onfocus(this: HTMLFrameSetElement, ev: FocusEvent): any; - onhashchange(this: HTMLFrameSetElement, ev: HashChangeEvent): any; - onload(this: HTMLFrameSetElement, ev: Event): any; - onmessage(this: HTMLFrameSetElement, ev: MessageEvent): any; - onoffline(this: HTMLFrameSetElement, ev: Event): any; - ononline(this: HTMLFrameSetElement, ev: Event): any; - onorientationchange(this: HTMLFrameSetElement, ev: Event): any; - onpagehide(this: HTMLFrameSetElement, ev: PageTransitionEvent): any; - onpageshow(this: HTMLFrameSetElement, ev: PageTransitionEvent): any; - onpopstate(this: HTMLFrameSetElement, ev: PopStateEvent): any; - onresize(this: HTMLFrameSetElement, ev: UIEvent): any; - onscroll(this: HTMLFrameSetElement, ev: UIEvent): any; - onstorage(this: HTMLFrameSetElement, ev: StorageEvent): any; - onunload(this: HTMLFrameSetElement, ev: Event): any; + onhashchange: (this: HTMLFrameSetElement, ev: HashChangeEvent) => any; + onmessage: (this: HTMLFrameSetElement, ev: MessageEvent) => any; + onoffline: (this: HTMLFrameSetElement, ev: Event) => any; + ononline: (this: HTMLFrameSetElement, ev: Event) => any; + onorientationchange: (this: HTMLFrameSetElement, ev: Event) => any; + onpagehide: (this: HTMLFrameSetElement, ev: PageTransitionEvent) => any; + onpageshow: (this: HTMLFrameSetElement, ev: PageTransitionEvent) => any; + onpopstate: (this: HTMLFrameSetElement, ev: PopStateEvent) => any; + onresize: (this: HTMLFrameSetElement, ev: UIEvent) => any; + onstorage: (this: HTMLFrameSetElement, ev: StorageEvent) => any; + onunload: (this: HTMLFrameSetElement, ev: Event) => any; /** * Sets or retrieves the frame heights of the object. */ @@ -5125,10 +5108,7 @@ interface HTMLIFrameElement extends HTMLElement, GetSVGDocument { * Sets or retrieves whether the user can resize the frame. */ noResize: boolean; - /** - * Raised when the object has been completely received from the server. - */ - onload(this: HTMLIFrameElement, ev: Event): any; + readonly sandbox: DOMSettableTokenList; /** * Sets or retrieves whether the frame can be scrolled. From f8e2cc13918c08c9697474c3cdc516fcee9596b0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Sep 2017 07:10:11 -0700 Subject: [PATCH 114/144] Properly flag and structurally compare marker type references --- src/compiler/checker.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4bacb88ef4a..fce58ad866f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8948,7 +8948,7 @@ namespace ts { if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - if (source.flags & TypeFlags.MarkerType && target.flags & TypeFlags.MarkerType) { + if (source.flags & TypeFlags.MarkerType && target.flags & TypeFlags.MarkerType && !(source.flags & TypeFlags.Object || target.flags & TypeFlags.Object)) { return source === markerSubType && target === markerSuperType ? Ternary.True : Ternary.False; } @@ -9414,9 +9414,11 @@ namespace ts { } } else { - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { - // We have type references to the same generic type. Obtain the variance information for the - // type parameters and relate the type arguments accordingly. + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && + !(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) { + // We have type references to the same generic type, and the type references are not marker + // type references (which we always compare structurally). Obtain the variance information + // for the type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { return result; @@ -9841,8 +9843,12 @@ namespace ts { } } - function getVarianceType(type: GenericType, source: TypeParameter, target: Type) { - return createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); + // Return a type reference where the source type parameter is replaced with the target marker + // type, and flag the result as a marker type reference. + function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) { + const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); + result.flags |= TypeFlags.MarkerType; + return result; } // Return an array containing the variance of each type parameter. The variance is effectively @@ -9867,15 +9873,15 @@ namespace ts { // We first compare instantiations where the type parameter is replaced with // marker types that have a known subtype relationship. From this we can infer // invariance, covariance, contravariance or bivariance. - const typeWithSuper = getVarianceType(type, tp, markerSuperType); - const typeWithSub = getVarianceType(type, tp, markerSubType); + const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType); + const typeWithSub = getMarkerTypeReference(type, tp, markerSubType); let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) | (isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0); // If the instantiations appear to be related bivariantly, it may be because the // type parameter is omnivariant (i.e. it isn't witnessed anywhere in the generic // type). To determine this we compare instantiations where the type parameter is // replaced with marker types that are known to be unrelated. - if (variance === Variance.Bivariant && isTypeAssignableTo(getVarianceType(type, tp, markerOtherType), typeWithSuper)) { + if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) { variance = Variance.Omnivariant; } variances.push(variance); From 589e1f440c7b76d359d8e9f9fd8722d199da3c59 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Sep 2017 08:52:22 -0700 Subject: [PATCH 115/144] Update comment --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fce58ad866f..574ce5c6d80 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9417,8 +9417,8 @@ namespace ts { if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker - // type references (which we always compare structurally). Obtain the variance information - // for the type parameters and relate the type arguments accordingly. + // type references (which are intended by be compared structurally). Obtain the variance + // information for the type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { return result; From afc8a261ccc6b19873032f9f5adb47b2ce874eef Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Sep 2017 21:31:11 -0700 Subject: [PATCH 116/144] Always perform structural comparison when variance check fails --- src/compiler/checker.ts | 117 ++++++++++++---------------------------- src/compiler/types.ts | 4 +- 2 files changed, 35 insertions(+), 86 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 574ce5c6d80..25dc3030556 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -282,9 +282,9 @@ namespace ts { const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const markerSuperType = createType(TypeFlags.MarkerType); - const markerSubType = createType(TypeFlags.MarkerType); - const markerOtherType = createType(TypeFlags.MarkerType); + const markerSuperType = createType(TypeFlags.TypeParameter); + const markerSubType = createType(TypeFlags.TypeParameter); + markerSubType.constraint = markerSuperType; const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); @@ -8948,10 +8948,6 @@ namespace ts { if (isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - if (source.flags & TypeFlags.MarkerType && target.flags & TypeFlags.MarkerType && !(source.flags & TypeFlags.Object || target.flags & TypeFlags.Object)) { - return source === markerSubType && target === markerSuperType ? Ternary.True : Ternary.False; - } - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && source.flags & TypeFlags.FreshLiteral) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { @@ -9219,41 +9215,15 @@ namespace ts { // in the process of computing variance information for recursive types and when // comparing 'this' type arguments. const variance = i < variances.length ? variances[i] : Variance.Covariant; - // We simply ignore omnivariant type arguments (because they're never witnessed). - if (variance !== Variance.Omnivariant) { - const s = sources[i]; - const t = targets[i]; - let related = Ternary.True; - if (variance === Variance.Covariant) { - related = isRelatedTo(s, t, reportErrors); - } - else if (variance === Variance.Contravariant) { - related = isRelatedTo(t, s, reportErrors); - } - else if (variance === Variance.Bivariant) { - // In the bivariant case we first compare contravariantly without reporting - // errors. Then, if that doesn't succeed, we compare covariantly with error - // reporting. Thus, error elaboration will be based on the the covariant check, - // which is generally easier to reason about. - related = isRelatedTo(t, s, /*reportErrors*/ false); - if (!related) { - related = isRelatedTo(s, t, reportErrors); - } - } - else { - // In the invariant case we first compare covariantly, and only when that - // succeeds do we proceed to compare contravariantly. Thus, error elaboration - // will typically be based on the covariant check. - related = isRelatedTo(s, t, reportErrors); - if (related) { - related &= isRelatedTo(t, s, reportErrors); - } - } - if (!related) { - return Ternary.False; - } - result &= related; + const s = sources[i]; + const t = targets[i]; + const related = variance === Variance.Covariant ? isRelatedTo(s, t, reportErrors) : + variance === Variance.Contravariant ? isRelatedTo(t, s, reportErrors) : + Ternary.False; + if (!related) { + return Ternary.False; } + result &= related; } return result; } @@ -9418,21 +9388,14 @@ namespace ts { !(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. + // information for the type parameters and relate the type arguments accordingly. If we do + // not succeed, fall through and do a structural comparison instead (there are instances + // where the variance information isn't accurate, e.g. when type parameters are used only + // in bivariant positions or when a type argument is 'any' or 'void'.) const variances = getVariances((source).target); if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { return result; } - // The type arguments did not relate appropriately, but it may be because getVariances was - // invoked recursively and returned emptyArray (in which case typeArgumentsRelatedTo defaulted - // to covariance for all type arguments). It might also be the case that the target type has a - // 'void' type argument for a covariant type parameter that is only used in return positions - // within the generic type (in which case any type argument is permitted on the source side). - // In those cases we proceed with a structural comparison. Otherwise, we know for certain the - // instantiations aren't related and we can return here. - if (variances !== emptyArray && !hasCovariantVoidArgument(target, variances)) { - return Ternary.False; - } } // Even if relationship doesn't hold for unions, intersections, or generic type references, // it may hold in a structural comparison. @@ -9851,13 +9814,18 @@ namespace ts { return result; } - // Return an array containing the variance of each type parameter. The variance is effectively - // a digest of the type comparisons that occur for each type argument when instantiations of the - // generic type are structurally compared. We infer the variance information by comparing - // instantiations of the generic type for type arguments with known relations. Note that the - // function returns the emptyArray singleton to signal that it has been invoked recursively for - // the given generic type. + // Return an array containing the variance of each type parameter. The variance information is + // computed by comparing instantiations of the generic type for type arguments with known relations. + // A type parameter is marked as covariant if a covariant comparison succeeds; otherwise, it is + // marked contravariant if a contravarint comparison succeeds; otherwise, it is marked invariant. + // One form of variance doesn't exclude another, so this information simply serves to indicate + // a "primary" relationship that can be checked as an optimization ahead of a full structural + // comparison. The function returns the emptyArray singleton if we're not in strictFunctionTypes + // mode or if the function has been invoked recursively for the given generic type. function getVariances(type: GenericType): Variance[] { + if (!strictFunctionTypes) { + return emptyArray; + } const typeParameters = type.typeParameters || emptyArray; let variances = type.variances; if (!variances) { @@ -9870,20 +9838,14 @@ namespace ts { type.variances = emptyArray; variances = []; for (const tp of typeParameters) { - // We first compare instantiations where the type parameter is replaced with - // marker types that have a known subtype relationship. From this we can infer - // invariance, covariance, contravariance or bivariance. + // We compare instantiations where the type parameter is replaced with marker types + // that have a known subtype relationship. From this we infer covariance, contravariance + // or invariance. const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType); const typeWithSub = getMarkerTypeReference(type, tp, markerSubType); - let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) | - (isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0); - // If the instantiations appear to be related bivariantly, it may be because the - // type parameter is omnivariant (i.e. it isn't witnessed anywhere in the generic - // type). To determine this we compare instantiations where the type parameter is - // replaced with marker types that are known to be unrelated. - if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) { - variance = Variance.Omnivariant; - } + const variance = isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : + isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : + Variance.Invariant; variances.push(variance); } } @@ -9892,17 +9854,6 @@ namespace ts { return variances; } - // Return true if the given type reference has a 'void' type argument for a covariant type parameter. - // See comment at call in recursiveTypeRelatedTo for when this case matters. - function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean { - for (let i = 0; i < variances.length; i++) { - if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) { - return true; - } - } - return false; - } - function isUnconstrainedTypeParameter(type: Type) { return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(type); } @@ -10709,9 +10660,9 @@ namespace ts { const sourceTypes = (source).typeArguments || emptyArray; const targetTypes = (target).typeArguments || emptyArray; const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; - const variances = strictFunctionTypes ? getVariances((source).target) : undefined; + const variances = getVariances((source).target); for (let i = 0; i < count; i++) { - if (variances && i < variances.length && variances[i] === Variance.Contravariant) { + if (i < variances.length && variances[i] === Variance.Contravariant) { inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); } else { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3f07716fd1b..8ab999032bc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3345,11 +3345,9 @@ namespace ts { } export const enum Variance { - Invariant = 0, // Both covariant and contravariant + Invariant = 0, // Neither covariant nor contravariant Covariant = 1, // Covariant Contravariant = 2, // Contravariant - Bivariant = 3, // Either covariant or contravariant - Omnivariant = 4 // Unwitnessed type parameter } // Generic class and interface types From 70e8f7364e813c5bff4ada3ecb581d31eeb38122 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Sep 2017 21:40:31 -0700 Subject: [PATCH 117/144] Add tests --- .../reference/strictFunctionTypes1.js | 25 ++ .../reference/strictFunctionTypes1.symbols | 96 +++++++ .../reference/strictFunctionTypes1.types | 102 +++++++ .../strictFunctionTypesErrors.errors.txt | 271 ++++++++++++++++++ .../reference/strictFunctionTypesErrors.js | 170 +++++++++++ tests/cases/compiler/strictFunctionTypes1.ts | 18 ++ .../compiler/strictFunctionTypesErrors.ts | 113 ++++++++ 7 files changed, 795 insertions(+) create mode 100644 tests/baselines/reference/strictFunctionTypes1.js create mode 100644 tests/baselines/reference/strictFunctionTypes1.symbols create mode 100644 tests/baselines/reference/strictFunctionTypes1.types create mode 100644 tests/baselines/reference/strictFunctionTypesErrors.errors.txt create mode 100644 tests/baselines/reference/strictFunctionTypesErrors.js create mode 100644 tests/cases/compiler/strictFunctionTypes1.ts create mode 100644 tests/cases/compiler/strictFunctionTypesErrors.ts diff --git a/tests/baselines/reference/strictFunctionTypes1.js b/tests/baselines/reference/strictFunctionTypes1.js new file mode 100644 index 00000000000..28a7a7f61d6 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypes1.js @@ -0,0 +1,25 @@ +//// [strictFunctionTypes1.ts] +declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; +declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; +declare function f3(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T; + +interface Func { (x: T): void } + +declare function f4(f1: Func, f2: Func): Func; + +declare function fo(x: Object): void; +declare function fs(x: string): void; +declare function fx(f: (x: "def") => void): void; + +const x1 = f1(fo, fs); // (x: string) => void +const x2 = f2("abc", fo, fs); // "abc" +const x3 = f3("abc", fo, fx); // "abc" | "def" +const x4 = f4(fo, fs); // Func + + +//// [strictFunctionTypes1.js] +"use strict"; +var x1 = f1(fo, fs); // (x: string) => void +var x2 = f2("abc", fo, fs); // "abc" +var x3 = f3("abc", fo, fx); // "abc" | "def" +var x4 = f4(fo, fs); // Func diff --git a/tests/baselines/reference/strictFunctionTypes1.symbols b/tests/baselines/reference/strictFunctionTypes1.symbols new file mode 100644 index 00000000000..70253411201 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypes1.symbols @@ -0,0 +1,96 @@ +=== tests/cases/compiler/strictFunctionTypes1.ts === +declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 0, 0)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 0, 20)) +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 0, 23)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 0, 28)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 0, 20)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 42)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 0, 48)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 0, 20)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 0, 65)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 0, 20)) + +declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 79)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 1, 20)) +>obj : Symbol(obj, Decl(strictFunctionTypes1.ts, 1, 23)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 1, 20)) +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 1, 30)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 1, 36)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 1, 20)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 1, 50)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 1, 56)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 1, 20)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 1, 20)) + +declare function f3(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T; +>f3 : Symbol(f3, Decl(strictFunctionTypes1.ts, 1, 74)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 2, 20)) +>obj : Symbol(obj, Decl(strictFunctionTypes1.ts, 2, 23)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 2, 20)) +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 2, 30)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 2, 36)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 2, 20)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 2, 50)) +>f : Symbol(f, Decl(strictFunctionTypes1.ts, 2, 56)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 2, 60)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 2, 20)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 2, 20)) + +interface Func { (x: T): void } +>Func : Symbol(Func, Decl(strictFunctionTypes1.ts, 2, 87)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 4, 15)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 4, 21)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 4, 15)) + +declare function f4(f1: Func, f2: Func): Func; +>f4 : Symbol(f4, Decl(strictFunctionTypes1.ts, 4, 34)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 6, 20)) +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 6, 23)) +>Func : Symbol(Func, Decl(strictFunctionTypes1.ts, 2, 87)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 6, 20)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 6, 35)) +>Func : Symbol(Func, Decl(strictFunctionTypes1.ts, 2, 87)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 6, 20)) +>Func : Symbol(Func, Decl(strictFunctionTypes1.ts, 2, 87)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 6, 20)) + +declare function fo(x: Object): void; +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 8, 20)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare function fs(x: string): void; +>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 9, 20)) + +declare function fx(f: (x: "def") => void): void; +>fx : Symbol(fx, Decl(strictFunctionTypes1.ts, 9, 37)) +>f : Symbol(f, Decl(strictFunctionTypes1.ts, 10, 20)) +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 10, 24)) + +const x1 = f1(fo, fs); // (x: string) => void +>x1 : Symbol(x1, Decl(strictFunctionTypes1.ts, 12, 5)) +>f1 : Symbol(f1, Decl(strictFunctionTypes1.ts, 0, 0)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) + +const x2 = f2("abc", fo, fs); // "abc" +>x2 : Symbol(x2, Decl(strictFunctionTypes1.ts, 13, 5)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 79)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) + +const x3 = f3("abc", fo, fx); // "abc" | "def" +>x3 : Symbol(x3, Decl(strictFunctionTypes1.ts, 14, 5)) +>f3 : Symbol(f3, Decl(strictFunctionTypes1.ts, 1, 74)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fx : Symbol(fx, Decl(strictFunctionTypes1.ts, 9, 37)) + +const x4 = f4(fo, fs); // Func +>x4 : Symbol(x4, Decl(strictFunctionTypes1.ts, 15, 5)) +>f4 : Symbol(f4, Decl(strictFunctionTypes1.ts, 4, 34)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) + diff --git a/tests/baselines/reference/strictFunctionTypes1.types b/tests/baselines/reference/strictFunctionTypes1.types new file mode 100644 index 00000000000..9701d78cff0 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypes1.types @@ -0,0 +1,102 @@ +=== tests/cases/compiler/strictFunctionTypes1.ts === +declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; +>f1 : (f1: (x: T) => void, f2: (x: T) => void) => (x: T) => void +>T : T +>f1 : (x: T) => void +>x : T +>T : T +>f2 : (x: T) => void +>x : T +>T : T +>x : T +>T : T + +declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; +>f2 : (obj: T, f1: (x: T) => void, f2: (x: T) => void) => T +>T : T +>obj : T +>T : T +>f1 : (x: T) => void +>x : T +>T : T +>f2 : (x: T) => void +>x : T +>T : T +>T : T + +declare function f3(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T; +>f3 : (obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T +>T : T +>obj : T +>T : T +>f1 : (x: T) => void +>x : T +>T : T +>f2 : (f: (x: T) => void) => void +>f : (x: T) => void +>x : T +>T : T +>T : T + +interface Func { (x: T): void } +>Func : Func +>T : T +>x : T +>T : T + +declare function f4(f1: Func, f2: Func): Func; +>f4 : (f1: Func, f2: Func) => Func +>T : T +>f1 : Func +>Func : Func +>T : T +>f2 : Func +>Func : Func +>T : T +>Func : Func +>T : T + +declare function fo(x: Object): void; +>fo : (x: Object) => void +>x : Object +>Object : Object + +declare function fs(x: string): void; +>fs : (x: string) => void +>x : string + +declare function fx(f: (x: "def") => void): void; +>fx : (f: (x: "def") => void) => void +>f : (x: "def") => void +>x : "def" + +const x1 = f1(fo, fs); // (x: string) => void +>x1 : (x: string) => void +>f1(fo, fs) : (x: string) => void +>f1 : (f1: (x: T) => void, f2: (x: T) => void) => (x: T) => void +>fo : (x: Object) => void +>fs : (x: string) => void + +const x2 = f2("abc", fo, fs); // "abc" +>x2 : "abc" +>f2("abc", fo, fs) : "abc" +>f2 : (obj: T, f1: (x: T) => void, f2: (x: T) => void) => T +>"abc" : "abc" +>fo : (x: Object) => void +>fs : (x: string) => void + +const x3 = f3("abc", fo, fx); // "abc" | "def" +>x3 : "def" | "abc" +>f3("abc", fo, fx) : "def" | "abc" +>f3 : (obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T +>"abc" : "abc" +>fo : (x: Object) => void +>fx : (f: (x: "def") => void) => void + +const x4 = f4(fo, fs); // Func +>x4 : Func +>f4(fo, fs) : Func +>f4 : (f1: Func, f2: Func) => Func +>fo : (x: Object) => void +>fs : (x: string) => void + diff --git a/tests/baselines/reference/strictFunctionTypesErrors.errors.txt b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt new file mode 100644 index 00000000000..3ff71ea26e7 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt @@ -0,0 +1,271 @@ +tests/cases/compiler/strictFunctionTypesErrors.ts(10,1): error TS2322: Type '(x: string) => Object' is not assignable to type '(x: Object) => Object'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(11,1): error TS2322: Type '(x: string) => string' is not assignable to type '(x: Object) => Object'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(13,1): error TS2322: Type '(x: Object) => Object' is not assignable to type '(x: Object) => string'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(14,1): error TS2322: Type '(x: string) => Object' is not assignable to type '(x: Object) => string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(15,1): error TS2322: Type '(x: string) => string' is not assignable to type '(x: Object) => string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(21,1): error TS2322: Type '(x: Object) => Object' is not assignable to type '(x: string) => string'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(23,1): error TS2322: Type '(x: string) => Object' is not assignable to type '(x: string) => string'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(33,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(34,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(36,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(37,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(38,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(44,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(46,1): error TS2322: Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(57,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(58,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(61,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, Object>'. + Type 'Func' is not assignable to type 'Func'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(62,1): error TS2322: Type 'Func, string>' is not assignable to type 'Func, Object>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(65,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(66,1): error TS2322: Type 'Func, string>' is not assignable to type 'Func, string>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(67,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(74,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(75,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(76,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(79,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(80,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Object' is not assignable to type 'string'. +tests/cases/compiler/strictFunctionTypesErrors.ts(83,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(84,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. + Type 'Func' is not assignable to type 'Func'. +tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Comparer2' is not assignable to type 'Comparer2'. + Type 'Animal' is not assignable to type 'Dog'. + + +==== tests/cases/compiler/strictFunctionTypesErrors.ts (29 errors) ==== + export {} + + + declare let f1: (x: Object) => Object; + declare let f2: (x: Object) => string; + declare let f3: (x: string) => Object; + declare let f4: (x: string) => string; + + f1 = f2; // Ok + f1 = f3; // Error + ~~ +!!! error TS2322: Type '(x: string) => Object' is not assignable to type '(x: Object) => Object'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + f1 = f4; // Error + ~~ +!!! error TS2322: Type '(x: string) => string' is not assignable to type '(x: Object) => Object'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + f2 = f1; // Error + ~~ +!!! error TS2322: Type '(x: Object) => Object' is not assignable to type '(x: Object) => string'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + f2 = f3; // Error + ~~ +!!! error TS2322: Type '(x: string) => Object' is not assignable to type '(x: Object) => string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + f2 = f4; // Error + ~~ +!!! error TS2322: Type '(x: string) => string' is not assignable to type '(x: Object) => string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + f3 = f1; // Ok + f3 = f2; // Ok + f3 = f4; // Ok + + f4 = f1; // Error + ~~ +!!! error TS2322: Type '(x: Object) => Object' is not assignable to type '(x: string) => string'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + f4 = f2; // Ok + f4 = f3; // Error + ~~ +!!! error TS2322: Type '(x: string) => Object' is not assignable to type '(x: string) => string'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + interface Func { (x: T): U } + + declare let g1: Func; + declare let g2: Func; + declare let g3: Func; + declare let g4: Func; + + g1 = g2; // Ok + g1 = g3; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + g1 = g4; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + g2 = g1; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + g2 = g3; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + g2 = g4; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + g3 = g1; // Ok + g3 = g2; // Ok + g3 = g4; // Ok + + g4 = g1; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + g4 = g2; // Ok + g4 = g3; // Error + ~~ +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + declare let h1: Func, Object>; + declare let h2: Func, string>; + declare let h3: Func, Object>; + declare let h4: Func, string>; + + h1 = h2; // Ok + h1 = h3; // Ok + h1 = h4; // Ok + + h2 = h1; // Error + ~~ +!!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + h2 = h3; // Error + ~~ +!!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + h2 = h4; // Ok + + h3 = h1; // Error + ~~ +!!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, Object>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + h3 = h2; // Error + ~~ +!!! error TS2322: Type 'Func, string>' is not assignable to type 'Func, Object>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + h3 = h4; // Ok + + h4 = h1; // Error + ~~ +!!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + h4 = h2; // Error + ~~ +!!! error TS2322: Type 'Func, string>' is not assignable to type 'Func, string>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + h4 = h3; // Error + ~~ +!!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + declare let i1: Func>; + declare let i2: Func>; + declare let i3: Func>; + declare let i4: Func>; + + i1 = i2; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + i1 = i3; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + i1 = i4; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + i2 = i1; // Ok + i2 = i3; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + i2 = i4; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. + + i3 = i1; // Ok + i3 = i2; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + i3 = i4; // Error + ~~ +!!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. +!!! error TS2322: Type 'Func' is not assignable to type 'Func'. + + i4 = i1; // Ok + i4 = i2; // Ok + i4 = i3; // Ok + + interface Animal { animal: void } + interface Dog extends Animal { dog: void } + interface Cat extends Animal { cat: void } + + interface Comparer1 { + compare(a: T, b: T): number; + } + + declare let animalComparer1: Comparer1; + declare let dogComparer1: Comparer1; + + animalComparer1 = dogComparer1; // Ok + dogComparer1 = animalComparer1; // Ok + + interface Comparer2 { + compare: (a: T, b: T) => number; + } + + declare let animalComparer2: Comparer2; + declare let dogComparer2: Comparer2; + + animalComparer2 = dogComparer2; // Error + ~~~~~~~~~~~~~~~ +!!! error TS2322: Type 'Comparer2' is not assignable to type 'Comparer2'. +!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'. + dogComparer2 = animalComparer2; // Ok + \ No newline at end of file diff --git a/tests/baselines/reference/strictFunctionTypesErrors.js b/tests/baselines/reference/strictFunctionTypesErrors.js new file mode 100644 index 00000000000..af41c9395f9 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypesErrors.js @@ -0,0 +1,170 @@ +//// [strictFunctionTypesErrors.ts] +export {} + + +declare let f1: (x: Object) => Object; +declare let f2: (x: Object) => string; +declare let f3: (x: string) => Object; +declare let f4: (x: string) => string; + +f1 = f2; // Ok +f1 = f3; // Error +f1 = f4; // Error + +f2 = f1; // Error +f2 = f3; // Error +f2 = f4; // Error + +f3 = f1; // Ok +f3 = f2; // Ok +f3 = f4; // Ok + +f4 = f1; // Error +f4 = f2; // Ok +f4 = f3; // Error + +interface Func { (x: T): U } + +declare let g1: Func; +declare let g2: Func; +declare let g3: Func; +declare let g4: Func; + +g1 = g2; // Ok +g1 = g3; // Error +g1 = g4; // Error + +g2 = g1; // Error +g2 = g3; // Error +g2 = g4; // Error + +g3 = g1; // Ok +g3 = g2; // Ok +g3 = g4; // Ok + +g4 = g1; // Error +g4 = g2; // Ok +g4 = g3; // Error + +declare let h1: Func, Object>; +declare let h2: Func, string>; +declare let h3: Func, Object>; +declare let h4: Func, string>; + +h1 = h2; // Ok +h1 = h3; // Ok +h1 = h4; // Ok + +h2 = h1; // Error +h2 = h3; // Error +h2 = h4; // Ok + +h3 = h1; // Error +h3 = h2; // Error +h3 = h4; // Ok + +h4 = h1; // Error +h4 = h2; // Error +h4 = h3; // Error + +declare let i1: Func>; +declare let i2: Func>; +declare let i3: Func>; +declare let i4: Func>; + +i1 = i2; // Error +i1 = i3; // Error +i1 = i4; // Error + +i2 = i1; // Ok +i2 = i3; // Error +i2 = i4; // Error + +i3 = i1; // Ok +i3 = i2; // Error +i3 = i4; // Error + +i4 = i1; // Ok +i4 = i2; // Ok +i4 = i3; // Ok + +interface Animal { animal: void } +interface Dog extends Animal { dog: void } +interface Cat extends Animal { cat: void } + +interface Comparer1 { + compare(a: T, b: T): number; +} + +declare let animalComparer1: Comparer1; +declare let dogComparer1: Comparer1; + +animalComparer1 = dogComparer1; // Ok +dogComparer1 = animalComparer1; // Ok + +interface Comparer2 { + compare: (a: T, b: T) => number; +} + +declare let animalComparer2: Comparer2; +declare let dogComparer2: Comparer2; + +animalComparer2 = dogComparer2; // Error +dogComparer2 = animalComparer2; // Ok + + +//// [strictFunctionTypesErrors.js] +"use strict"; +exports.__esModule = true; +f1 = f2; // Ok +f1 = f3; // Error +f1 = f4; // Error +f2 = f1; // Error +f2 = f3; // Error +f2 = f4; // Error +f3 = f1; // Ok +f3 = f2; // Ok +f3 = f4; // Ok +f4 = f1; // Error +f4 = f2; // Ok +f4 = f3; // Error +g1 = g2; // Ok +g1 = g3; // Error +g1 = g4; // Error +g2 = g1; // Error +g2 = g3; // Error +g2 = g4; // Error +g3 = g1; // Ok +g3 = g2; // Ok +g3 = g4; // Ok +g4 = g1; // Error +g4 = g2; // Ok +g4 = g3; // Error +h1 = h2; // Ok +h1 = h3; // Ok +h1 = h4; // Ok +h2 = h1; // Error +h2 = h3; // Error +h2 = h4; // Ok +h3 = h1; // Error +h3 = h2; // Error +h3 = h4; // Ok +h4 = h1; // Error +h4 = h2; // Error +h4 = h3; // Error +i1 = i2; // Error +i1 = i3; // Error +i1 = i4; // Error +i2 = i1; // Ok +i2 = i3; // Error +i2 = i4; // Error +i3 = i1; // Ok +i3 = i2; // Error +i3 = i4; // Error +i4 = i1; // Ok +i4 = i2; // Ok +i4 = i3; // Ok +animalComparer1 = dogComparer1; // Ok +dogComparer1 = animalComparer1; // Ok +animalComparer2 = dogComparer2; // Error +dogComparer2 = animalComparer2; // Ok diff --git a/tests/cases/compiler/strictFunctionTypes1.ts b/tests/cases/compiler/strictFunctionTypes1.ts new file mode 100644 index 00000000000..7d8ffc689ff --- /dev/null +++ b/tests/cases/compiler/strictFunctionTypes1.ts @@ -0,0 +1,18 @@ +// @strict: true + +declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; +declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; +declare function f3(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T; + +interface Func { (x: T): void } + +declare function f4(f1: Func, f2: Func): Func; + +declare function fo(x: Object): void; +declare function fs(x: string): void; +declare function fx(f: (x: "def") => void): void; + +const x1 = f1(fo, fs); // (x: string) => void +const x2 = f2("abc", fo, fs); // "abc" +const x3 = f3("abc", fo, fx); // "abc" | "def" +const x4 = f4(fo, fs); // Func diff --git a/tests/cases/compiler/strictFunctionTypesErrors.ts b/tests/cases/compiler/strictFunctionTypesErrors.ts new file mode 100644 index 00000000000..5c6909e6806 --- /dev/null +++ b/tests/cases/compiler/strictFunctionTypesErrors.ts @@ -0,0 +1,113 @@ +export {} + +// @strict: true + +declare let f1: (x: Object) => Object; +declare let f2: (x: Object) => string; +declare let f3: (x: string) => Object; +declare let f4: (x: string) => string; + +f1 = f2; // Ok +f1 = f3; // Error +f1 = f4; // Error + +f2 = f1; // Error +f2 = f3; // Error +f2 = f4; // Error + +f3 = f1; // Ok +f3 = f2; // Ok +f3 = f4; // Ok + +f4 = f1; // Error +f4 = f2; // Ok +f4 = f3; // Error + +interface Func { (x: T): U } + +declare let g1: Func; +declare let g2: Func; +declare let g3: Func; +declare let g4: Func; + +g1 = g2; // Ok +g1 = g3; // Error +g1 = g4; // Error + +g2 = g1; // Error +g2 = g3; // Error +g2 = g4; // Error + +g3 = g1; // Ok +g3 = g2; // Ok +g3 = g4; // Ok + +g4 = g1; // Error +g4 = g2; // Ok +g4 = g3; // Error + +declare let h1: Func, Object>; +declare let h2: Func, string>; +declare let h3: Func, Object>; +declare let h4: Func, string>; + +h1 = h2; // Ok +h1 = h3; // Ok +h1 = h4; // Ok + +h2 = h1; // Error +h2 = h3; // Error +h2 = h4; // Ok + +h3 = h1; // Error +h3 = h2; // Error +h3 = h4; // Ok + +h4 = h1; // Error +h4 = h2; // Error +h4 = h3; // Error + +declare let i1: Func>; +declare let i2: Func>; +declare let i3: Func>; +declare let i4: Func>; + +i1 = i2; // Error +i1 = i3; // Error +i1 = i4; // Error + +i2 = i1; // Ok +i2 = i3; // Error +i2 = i4; // Error + +i3 = i1; // Ok +i3 = i2; // Error +i3 = i4; // Error + +i4 = i1; // Ok +i4 = i2; // Ok +i4 = i3; // Ok + +interface Animal { animal: void } +interface Dog extends Animal { dog: void } +interface Cat extends Animal { cat: void } + +interface Comparer1 { + compare(a: T, b: T): number; +} + +declare let animalComparer1: Comparer1; +declare let dogComparer1: Comparer1; + +animalComparer1 = dogComparer1; // Ok +dogComparer1 = animalComparer1; // Ok + +interface Comparer2 { + compare: (a: T, b: T) => number; +} + +declare let animalComparer2: Comparer2; +declare let dogComparer2: Comparer2; + +animalComparer2 = dogComparer2; // Error +dogComparer2 = animalComparer2; // Ok From 91691f6079edaf77dce77df5e0c0d1176bc0a7b8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Sep 2017 16:59:18 -0700 Subject: [PATCH 118/144] Strict function type checking only for certain function types --- src/compiler/checker.ts | 120 +++++++++++++++++++++++++++++----------- src/compiler/types.ts | 2 + 2 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 25dc3030556..5d3957652d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -285,6 +285,7 @@ namespace ts { const markerSuperType = createType(TypeFlags.TypeParameter); const markerSubType = createType(TypeFlags.TypeParameter); markerSubType.constraint = markerSuperType; + const markerOtherType = createType(TypeFlags.TypeParameter); const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); @@ -3548,7 +3549,7 @@ namespace ts { return; } - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length && isStrictSignature(resolved.callSignatures[0])) { const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); if (parenthesizeSignature) { writePunctuation(writer, SyntaxKind.OpenParenToken); @@ -3559,7 +3560,7 @@ namespace ts { } return; } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length && isStrictSignature(resolved.constructSignatures[0])) { if (flags & TypeFormatFlags.InElementType) { writePunctuation(writer, SyntaxKind.OpenParenToken); } @@ -8521,6 +8522,17 @@ namespace ts { /*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False; } + // A signature is considered strict if it is declared in a function type literal, a constructor type + // literal, a function expression, an arrow function, or a function declaration with no overloads. A + // strict signature is subject to strict checking in strictFunctionTypes mode. + function isStrictSignature(signature: Signature) { + const declaration = signature.declaration; + const kind = declaration ? declaration.kind : SyntaxKind.Unknown; + return kind === SyntaxKind.FunctionType || kind === SyntaxKind.ConstructorType || + kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction || + (kind === SyntaxKind.FunctionDeclaration && getSingleCallSignature(getTypeOfSymbol(getSymbolOfNode(declaration)))); + } + type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; /** @@ -8546,9 +8558,7 @@ namespace ts { source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); } - const targetKind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; - const strictVariance = strictFunctionTypes && targetKind !== SyntaxKind.MethodDeclaration && targetKind !== SyntaxKind.MethodSignature; - + const strictVariance = strictFunctionTypes && isStrictSignature(target); let result = Ternary.True; const sourceThisType = getThisTypeOfSignature(source); @@ -9215,15 +9225,41 @@ namespace ts { // in the process of computing variance information for recursive types and when // comparing 'this' type arguments. const variance = i < variances.length ? variances[i] : Variance.Covariant; - const s = sources[i]; - const t = targets[i]; - const related = variance === Variance.Covariant ? isRelatedTo(s, t, reportErrors) : - variance === Variance.Contravariant ? isRelatedTo(t, s, reportErrors) : - Ternary.False; - if (!related) { - return Ternary.False; + // We ignore arguments for independent type parameters (because they're never witnessed). + if (variance !== Variance.Independent) { + const s = sources[i]; + const t = targets[i]; + let related = Ternary.True; + if (variance === Variance.Covariant) { + related = isRelatedTo(s, t, reportErrors); + } + else if (variance === Variance.Contravariant) { + related = isRelatedTo(t, s, reportErrors); + } + else if (variance === Variance.Bivariant) { + // In the bivariant case we first compare contravariantly without reporting + // errors. Then, if that doesn't succeed, we compare covariantly with error + // reporting. Thus, error elaboration will be based on the the covariant check, + // which is generally easier to reason about. + related = isRelatedTo(t, s, /*reportErrors*/ false); + if (!related) { + related = isRelatedTo(s, t, reportErrors); + } + } + else { + // In the invariant case we first compare covariantly, and only when that + // succeeds do we proceed to compare contravariantly. Thus, error elaboration + // will typically be based on the covariant check. + related = isRelatedTo(s, t, reportErrors); + if (related) { + related &= isRelatedTo(t, s, reportErrors); + } + } + if (!related) { + return Ternary.False; + } + result &= related; } - result &= related; } return result; } @@ -9388,14 +9424,21 @@ namespace ts { !(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker // type references (which are intended by be compared structurally). Obtain the variance - // information for the type parameters and relate the type arguments accordingly. If we do - // not succeed, fall through and do a structural comparison instead (there are instances - // where the variance information isn't accurate, e.g. when type parameters are used only - // in bivariant positions or when a type argument is 'any' or 'void'.) + // information for the type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); if (result = typeArgumentsRelatedTo(source, target, variances, reportErrors)) { return result; } + // The type arguments did not relate appropriately, but it may be because we have no variance + // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type + // arguments). It might also be the case that the target type has a 'void' type argument for + // a covariant type parameter that is only used in return positions within the generic type + // (in which case any type argument is permitted on the source side). In those cases we proceed + // with a structural comparison. Otherwise, we know for certain the instantiations aren't + // related and we can return here. + if (variances !== emptyArray && !hasCovariantVoidArgument(target, variances)) { + return Ternary.False; + } } // Even if relationship doesn't hold for unions, intersections, or generic type references, // it may hold in a structural comparison. @@ -9814,14 +9857,12 @@ namespace ts { return result; } - // Return an array containing the variance of each type parameter. The variance information is - // computed by comparing instantiations of the generic type for type arguments with known relations. - // A type parameter is marked as covariant if a covariant comparison succeeds; otherwise, it is - // marked contravariant if a contravarint comparison succeeds; otherwise, it is marked invariant. - // One form of variance doesn't exclude another, so this information simply serves to indicate - // a "primary" relationship that can be checked as an optimization ahead of a full structural - // comparison. The function returns the emptyArray singleton if we're not in strictFunctionTypes - // mode or if the function has been invoked recursively for the given generic type. + // Return an array containing the variance of each type parameter. The variance is effectively + // a digest of the type comparisons that occur for each type argument when instantiations of the + // generic type are structurally compared. We infer the variance information by comparing + // instantiations of the generic type for type arguments with known relations. The function + // returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function + // has been invoked recursively for the given generic type. function getVariances(type: GenericType): Variance[] { if (!strictFunctionTypes) { return emptyArray; @@ -9838,14 +9879,20 @@ namespace ts { type.variances = emptyArray; variances = []; for (const tp of typeParameters) { - // We compare instantiations where the type parameter is replaced with marker types - // that have a known subtype relationship. From this we infer covariance, contravariance - // or invariance. + // We first compare instantiations where the type parameter is replaced with + // marker types that have a known subtype relationship. From this we can infer + // invariance, covariance, contravariance or bivariance. const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType); const typeWithSub = getMarkerTypeReference(type, tp, markerSubType); - const variance = isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : - isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : - Variance.Invariant; + let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) | + (isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0); + // If the instantiations appear to be related bivariantly it may be because the + // type parameter is independent (i.e. it isn't witnessed anywhere in the generic + // type). To determine this we compare instantiations where the type parameter is + // replaced with marker types that are known to be unrelated. + if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) { + variance = Variance.Independent; + } variances.push(variance); } } @@ -9854,6 +9901,17 @@ namespace ts { return variances; } + // Return true if the given type reference has a 'void' type argument for a covariant type parameter. + // See comment at call in recursiveTypeRelatedTo for when this case matters. + function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean { + for (let i = 0; i < variances.length; i++) { + if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) { + return true; + } + } + return false; + } + function isUnconstrainedTypeParameter(type: Type) { return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(type); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8ab999032bc..a867d9ebe2a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3348,6 +3348,8 @@ namespace ts { Invariant = 0, // Neither covariant nor contravariant Covariant = 1, // Covariant Contravariant = 2, // Contravariant + Bivariant = 3, // Both covariant and contravariant + Independent = 4, // Unwitnessed type parameter } // Generic class and interface types From 6a481e8ddc1431924b67c7d17216f3d96d0b7db7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Sep 2017 16:59:39 -0700 Subject: [PATCH 119/144] Update tests --- .../strictFunctionTypesErrors.errors.txt | 90 +++++++++++++------ .../reference/strictFunctionTypesErrors.js | 2 +- .../compiler/strictFunctionTypesErrors.ts | 2 +- 3 files changed, 65 insertions(+), 29 deletions(-) diff --git a/tests/baselines/reference/strictFunctionTypesErrors.errors.txt b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt index 3ff71ea26e7..eb0c2d00233 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.errors.txt +++ b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt @@ -17,15 +17,19 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(21,1): error TS2322: Type '(x: tests/cases/compiler/strictFunctionTypesErrors.ts(23,1): error TS2322: Type '(x: string) => Object' is not assignable to type '(x: string) => string'. Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(33,1): error TS2322: Type 'Func' is not assignable to type 'Func'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(34,1): error TS2322: Type 'Func' is not assignable to type 'Func'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(36,1): error TS2322: Type 'Func' is not assignable to type 'Func'. Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(37,1): error TS2322: Type 'Func' is not assignable to type 'Func'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(38,1): error TS2322: Type 'Func' is not assignable to type 'Func'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(44,1): error TS2322: Type 'Func' is not assignable to type 'Func'. Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(46,1): error TS2322: Type 'Func' is not assignable to type 'Func'. @@ -35,32 +39,46 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(57,1): error TS2322: Type 'Fun tests/cases/compiler/strictFunctionTypesErrors.ts(58,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(61,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, Object>'. - Type 'Func' is not assignable to type 'Func'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(62,1): error TS2322: Type 'Func, string>' is not assignable to type 'Func, Object>'. - Type 'Func' is not assignable to type 'Func'. + Types of parameters 'x' and 'x' are incompatible. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(65,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. - Type 'Func' is not assignable to type 'Func'. + Types of parameters 'x' and 'x' are incompatible. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(66,1): error TS2322: Type 'Func, string>' is not assignable to type 'Func, string>'. - Type 'Func' is not assignable to type 'Func'. + Types of parameters 'x' and 'x' are incompatible. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(67,1): error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(74,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. Type 'Func' is not assignable to type 'Func'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(75,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(76,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(79,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(80,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. - Type 'Object' is not assignable to type 'string'. + Types of parameters 'x' and 'x' are incompatible. + Type 'Object' is not assignable to type 'string'. tests/cases/compiler/strictFunctionTypesErrors.ts(83,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. Type 'Func' is not assignable to type 'Func'. tests/cases/compiler/strictFunctionTypesErrors.ts(84,1): error TS2322: Type 'Func>' is not assignable to type 'Func>'. Type 'Func' is not assignable to type 'Func'. tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Comparer2' is not assignable to type 'Comparer2'. Type 'Animal' is not assignable to type 'Dog'. + Property 'dog' is missing in type 'Animal'. ==== tests/cases/compiler/strictFunctionTypesErrors.ts (29 errors) ==== @@ -113,7 +131,7 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co !!! error TS2322: Type '(x: string) => Object' is not assignable to type '(x: string) => string'. !!! error TS2322: Type 'Object' is not assignable to type 'string'. - interface Func { (x: T): U } + type Func = (x: T) => U; declare let g1: Func; declare let g2: Func; @@ -124,11 +142,13 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co g1 = g3; // Error ~~ !!! error TS2322: Type 'Func' is not assignable to type 'Func'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. g1 = g4; // Error ~~ !!! error TS2322: Type 'Func' is not assignable to type 'Func'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. g2 = g1; // Error ~~ @@ -137,11 +157,13 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co g2 = g3; // Error ~~ !!! error TS2322: Type 'Func' is not assignable to type 'Func'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. g2 = g4; // Error ~~ !!! error TS2322: Type 'Func' is not assignable to type 'Func'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. g3 = g1; // Ok g3 = g2; // Ok @@ -179,22 +201,29 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co h3 = h1; // Error ~~ !!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, Object>'. -!!! error TS2322: Type 'Func' is not assignable to type 'Func'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. h3 = h2; // Error ~~ !!! error TS2322: Type 'Func, string>' is not assignable to type 'Func, Object>'. -!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. h3 = h4; // Ok h4 = h1; // Error ~~ !!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. -!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. h4 = h2; // Error ~~ !!! error TS2322: Type 'Func, string>' is not assignable to type 'Func, string>'. -!!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. h4 = h3; // Error ~~ !!! error TS2322: Type 'Func, Object>' is not assignable to type 'Func, string>'. @@ -209,24 +238,30 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co ~~ !!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. !!! error TS2322: Type 'Func' is not assignable to type 'Func'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. i1 = i3; // Error ~~ !!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. i1 = i4; // Error ~~ !!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. i2 = i1; // Ok i2 = i3; // Error ~~ !!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. i2 = i4; // Error ~~ !!! error TS2322: Type 'Func>' is not assignable to type 'Func>'. -!!! error TS2322: Type 'Object' is not assignable to type 'string'. +!!! error TS2322: Types of parameters 'x' and 'x' are incompatible. +!!! error TS2322: Type 'Object' is not assignable to type 'string'. i3 = i1; // Ok i3 = i2; // Error @@ -267,5 +302,6 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co ~~~~~~~~~~~~~~~ !!! error TS2322: Type 'Comparer2' is not assignable to type 'Comparer2'. !!! error TS2322: Type 'Animal' is not assignable to type 'Dog'. +!!! error TS2322: Property 'dog' is missing in type 'Animal'. dogComparer2 = animalComparer2; // Ok \ No newline at end of file diff --git a/tests/baselines/reference/strictFunctionTypesErrors.js b/tests/baselines/reference/strictFunctionTypesErrors.js index af41c9395f9..0049fe970ce 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.js +++ b/tests/baselines/reference/strictFunctionTypesErrors.js @@ -23,7 +23,7 @@ f4 = f1; // Error f4 = f2; // Ok f4 = f3; // Error -interface Func { (x: T): U } +type Func = (x: T) => U; declare let g1: Func; declare let g2: Func; diff --git a/tests/cases/compiler/strictFunctionTypesErrors.ts b/tests/cases/compiler/strictFunctionTypesErrors.ts index 5c6909e6806..32029364d76 100644 --- a/tests/cases/compiler/strictFunctionTypesErrors.ts +++ b/tests/cases/compiler/strictFunctionTypesErrors.ts @@ -23,7 +23,7 @@ f4 = f1; // Error f4 = f2; // Ok f4 = f3; // Error -interface Func { (x: T): U } +type Func = (x: T) => U; declare let g1: Func; declare let g2: Func; From 1795614c415264834b1b8552d1fac5607ed7dda0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Sep 2017 17:06:09 -0700 Subject: [PATCH 120/144] Accept new baselines --- .../reference/commentsClassMembers.js | 8 +++-- .../baselines/reference/commentsInterface.js | 8 +++-- tests/baselines/reference/jsDocTypeTag2.js | 30 +++++++++++++++---- .../typeGuardFunctionOfFormThisErrors.js | 4 ++- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/commentsClassMembers.js b/tests/baselines/reference/commentsClassMembers.js index f53c64060a3..d5cee0e1950 100644 --- a/tests/baselines/reference/commentsClassMembers.js +++ b/tests/baselines/reference/commentsClassMembers.js @@ -548,11 +548,15 @@ declare class c1 { } declare var i1: c1; declare var i1_p: number; -declare var i1_f: (b: number) => number; +declare var i1_f: { + (b: number): number; +}; declare var i1_r: number; declare var i1_prop: number; declare var i1_nc_p: number; -declare var i1_ncf: (b: number) => number; +declare var i1_ncf: { + (b: number): number; +}; declare var i1_ncr: number; declare var i1_ncprop: number; declare var i1_s_p: number; diff --git a/tests/baselines/reference/commentsInterface.js b/tests/baselines/reference/commentsInterface.js index 3deac9871f1..b7ceffa58af 100644 --- a/tests/baselines/reference/commentsInterface.js +++ b/tests/baselines/reference/commentsInterface.js @@ -142,9 +142,13 @@ declare var i2_i_nc_x: number; declare var i2_i_nc_foo: (b: number) => string; declare var i2_i_nc_foo_r: string; declare var i2_i_r: number; -declare var i2_i_fnfoo: (b: number) => string; +declare var i2_i_fnfoo: { + (b: number): string; +}; declare var i2_i_fnfoo_r: string; -declare var i2_i_nc_fnfoo: (b: number) => string; +declare var i2_i_nc_fnfoo: { + (b: number): string; +}; declare var i2_i_nc_fnfoo_r: string; interface i3 { /** Comment i3 x*/ diff --git a/tests/baselines/reference/jsDocTypeTag2.js b/tests/baselines/reference/jsDocTypeTag2.js index d54b4557f06..bde3f3f9c1e 100644 --- a/tests/baselines/reference/jsDocTypeTag2.js +++ b/tests/baselines/reference/jsDocTypeTag2.js @@ -472,6 +472,18 @@ "text": " ", "kind": "space" }, + { + "text": "{", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": " ", + "kind": "space" + }, { "text": "(", "kind": "punctuation" @@ -497,11 +509,7 @@ "kind": "punctuation" }, { - "text": " ", - "kind": "space" - }, - { - "text": "=>", + "text": ":", "kind": "punctuation" }, { @@ -511,6 +519,18 @@ { "text": "number", "kind": "keyword" + }, + { + "text": ";", + "kind": "punctuation" + }, + { + "text": "\n", + "kind": "lineBreak" + }, + { + "text": "}", + "kind": "punctuation" } ], "documentation": [], diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js index 84db5d4a4a9..6ba90de54f1 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js @@ -154,4 +154,6 @@ declare let c: number | number[]; declare let holder: { invalidGuard: (c: any) => this is number; }; -declare let detached: () => this is FollowerGuard; +declare let detached: { + (): this is FollowerGuard; +}; From 14febe211308082ae0875bc74da95a3ee4bb6c1a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 10:46:32 -0700 Subject: [PATCH 121/144] Rename watchedProgram.ts to watch.ts --- src/compiler/tsc.ts | 2 +- src/compiler/tsconfig.json | 2 +- src/compiler/{watchedProgram.ts => watch.ts} | 0 src/harness/unittests/tscWatchMode.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/compiler/{watchedProgram.ts => watch.ts} (100%) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 57c906c7fb6..680daeeb485 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -1,5 +1,5 @@ /// -/// +/// /// namespace ts { diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index f527610cbd7..dee33cbf163 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -39,7 +39,7 @@ "program.ts", "builder.ts", "resolutionCache.ts", - "watchedProgram.ts", + "watch.ts", "commandLineParser.ts", "tsc.ts", "diagnosticInformationMap.generated.ts" diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watch.ts similarity index 100% rename from src/compiler/watchedProgram.ts rename to src/compiler/watch.ts diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index def410c6b6b..5267bbcf047 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1,5 +1,5 @@ /// -/// +/// /// namespace ts.tscWatch { From 38f3a2b700a89df96b9e119d9ecf573e133e890a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 11:05:52 -0700 Subject: [PATCH 122/144] Renamed PartialSystem as DirectoryStructureHost and CachedPartialSystem as CachedDirectoryStructureHost --- src/compiler/core.ts | 7 ++---- src/compiler/resolutionCache.ts | 10 ++++----- src/compiler/sys.ts | 7 ++---- src/compiler/watch.ts | 36 +++++++++++++++---------------- src/server/editorServices.ts | 26 +++++++++++----------- src/server/project.ts | 38 ++++++++++++++++----------------- src/server/scriptInfo.ts | 2 +- 7 files changed, 60 insertions(+), 66 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 11f9d0a9e24..9579dec51cd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2668,21 +2668,18 @@ namespace ts { export function assertTypeIsNever(_: never): void { } - export interface CachedHost { + export interface CachedDirectoryStructureHost extends DirectoryStructureHost { addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; clearCache(): void; } - export interface CachedPartialSystem extends PartialSystem, CachedHost { - } - interface MutableFileSystemEntries { readonly files: string[]; readonly directories: string[]; } - export function createCachedPartialSystem(host: PartialSystem): CachedPartialSystem { + export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index a320e754d85..814d5053341 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -42,7 +42,7 @@ namespace ts { onInvalidatedResolution(): void; watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onChangedAutomaticTypeDirectiveNames(): void; - getCachedPartialSystem?(): CachedPartialSystem; + getCachedDirectoryStructureHost?(): CachedDirectoryStructureHost; projectName?: string; getGlobalCache?(): string | undefined; writeLog(s: string): void; @@ -396,9 +396,9 @@ namespace ts { function createDirectoryWatcher(directory: string, dirPath: Path) { return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => { const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); - if (resolutionHost.getCachedPartialSystem) { + if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process @@ -515,9 +515,9 @@ namespace ts { // Create new watch and recursive info return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => { const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); - if (resolutionHost.getCachedPartialSystem) { + if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 5f3b8bd06d1..822de1eef52 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -33,7 +33,7 @@ namespace ts { /** * Partial interface of the System thats needed to support the caching of directory structure */ - export interface PartialSystem { + export interface DirectoryStructureHost { newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; @@ -48,11 +48,8 @@ namespace ts { exit(exitCode?: number): void; } - export interface System extends PartialSystem { + export interface System extends DirectoryStructureHost { args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 939e50ce2ab..2d1d6e8bf58 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -4,7 +4,7 @@ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; + export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; export interface WatchingSystemHost { // FS system to use system: System; @@ -18,7 +18,7 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: PartialSystem, program: Program, builder: Builder): void; + afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -61,7 +61,7 @@ namespace ts { system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); } - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { let configFileText: string; try { configFileText = system.readFile(configFileName); @@ -89,7 +89,7 @@ namespace ts { return configParseResult; } - function reportEmittedFiles(files: string[], system: PartialSystem): void { + function reportEmittedFiles(files: string[], system: DirectoryStructureHost): void { if (!files || files.length === 0) { return; } @@ -100,7 +100,7 @@ namespace ts { } } - export function handleEmitOutputAndReportErrors(system: PartialSystem, program: Program, + export function handleEmitOutputAndReportErrors(system: DirectoryStructureHost, program: Program, emittedFiles: string[], emitSkipped: boolean, diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter ): ExitStatus { @@ -141,7 +141,7 @@ namespace ts { afterCompile: compileWatchedProgram, }; - function compileWatchedProgram(host: PartialSystem, program: Program, builder: Builder) { + function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) { // First get and report any syntactic errors. let diagnostics = program.getSyntacticDiagnostics().slice(); let reportSemanticDiagnostics = false; @@ -256,14 +256,14 @@ namespace ts { watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - const partialSystem = configFileName ? createCachedPartialSystem(system) : system; + const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName) { watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const getCurrentDirectory = memoize(() => partialSystem.getCurrentDirectory()); + const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); const realpath = system.realpath && ((path: string) => system.realpath(path)); - const getCachedPartialSystem = configFileName && (() => partialSystem as CachedPartialSystem); + const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); let newLine = getNewLineCharacter(compilerOptions, system); @@ -294,7 +294,7 @@ namespace ts { getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation: watchDirectory, watchTypeRootsDirectory: watchDirectory, - getCachedPartialSystem, + getCachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, onChangedAutomaticTypeDirectiveNames, writeLog @@ -361,7 +361,7 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(partialSystem, program, builder); + afterCompile(directoryStructureHost, program, builder); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } @@ -376,11 +376,11 @@ namespace ts { return !isString(hostSourceFileInfo); } - return partialSystem.fileExists(fileName); + return directoryStructureHost.fileExists(fileName); } function directoryExists(directoryName: string) { - return partialSystem.directoryExists(directoryName); + return directoryStructureHost.directoryExists(directoryName); } function readFile(fileName: string) { @@ -392,7 +392,7 @@ namespace ts { } function getDirectories(path: string) { - return partialSystem.getDirectories(path); + return directoryStructureHost.getDirectories(path); } function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]) { @@ -541,7 +541,7 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); needsReload = false; - const cachedHost = partialSystem as CachedPartialSystem; + const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; cachedHost.clearCache(); const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; @@ -586,7 +586,7 @@ namespace ts { function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - (partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); + (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, path, eventKind); } } @@ -639,7 +639,7 @@ namespace ts { const fileOrFolderPath = toPath(fileOrFolder); // Since the file existance changed, update the sourceFiles cache - (partialSystem as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); removeSourceFile(fileOrFolderPath); // If the the added or created file or folder is not supported file name, ignore the file @@ -651,7 +651,7 @@ namespace ts { // Reload is pending, do the reload if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, partialSystem); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b0d9e2ccf04..c93098c32c2 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -755,7 +755,7 @@ namespace ts.server { directory, fileOrFolder => { const fileOrFolderPath = this.toPath(fileOrFolder); - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + project.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); const configFilename = project.getConfigFilePath(); // If the the added or created file or folder is not supported file name, ignore the file @@ -768,7 +768,7 @@ namespace ts.server { // Reload is pending, do the reload if (!project.pendingReload) { const configFileSpecs = project.configFileSpecs; - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions); project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); @@ -1292,7 +1292,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedPartialSystem: CachedPartialSystem) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedDirectoryStructureHost: CachedDirectoryStructureHost) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1304,7 +1304,7 @@ namespace ts.server { const errors = result.parseDiagnostics; const parsedCommandLine = parseJsonSourceFileConfigFileContent( result, - cachedPartialSystem, + cachedDirectoryStructureHost, getDirectoryPath(configFilename), /*existingOptions*/ {}, configFilename, @@ -1429,8 +1429,8 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath) { - const cachedPartialSystem = createCachedPartialSystem(this.host); - const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedPartialSystem); + const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host); + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( @@ -1441,7 +1441,7 @@ namespace ts.server { projectOptions.compilerOptions, languageServiceEnabled, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, - cachedPartialSystem); + cachedDirectoryStructureHost); project.configFileSpecs = configFileSpecs; // TODO: We probably should also watch the configFiles that are extended @@ -1488,7 +1488,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.partialSystem); + scriptInfo = this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(normalizedPath, scriptKind, hasMixedContent, project.directoryStructureHost); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { @@ -1539,7 +1539,7 @@ namespace ts.server { /* @internal */ reloadConfiguredProject(project: ConfiguredProject) { // At this point, there is no reason to not have configFile in the host - const host = project.getCachedPartialSystem(); + const host = project.getCachedDirectoryStructureHost(); // Clear the cache since we are reloading the project from disk host.clearCache(); @@ -1637,7 +1637,7 @@ namespace ts.server { } /*@internal*/ - getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, hostToQueryFileExistsOn: PartialSystem) { + getOrCreateScriptInfoNotOpenedByClient(uncheckedFileName: string, hostToQueryFileExistsOn: DirectoryStructureHost) { return this.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath( toNormalizedPath(uncheckedFileName), /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn @@ -1669,15 +1669,15 @@ namespace ts.server { } } - getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } - getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content"); const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); diff --git a/src/server/project.ts b/src/server/project.ts index a094054c821..746e6bf3fbe 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -201,7 +201,7 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - /*@internal*/public partialSystem: PartialSystem) { + /*@internal*/public directoryStructureHost: DirectoryStructureHost) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -236,7 +236,7 @@ namespace ts.server { } getNewLine() { - return this.partialSystem.newLine; + return this.directoryStructureHost.newLine; } getProjectVersion() { @@ -263,7 +263,7 @@ namespace ts.server { } private getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.partialSystem); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); if (existingValue !== undefined && existingValue !== scriptInfo) { @@ -298,7 +298,7 @@ namespace ts.server { } getCurrentDirectory(): string { - return this.partialSystem.getCurrentDirectory(); + return this.directoryStructureHost.getCurrentDirectory(); } getDefaultLibFileName() { @@ -307,22 +307,22 @@ namespace ts.server { } useCaseSensitiveFileNames() { - return this.partialSystem.useCaseSensitiveFileNames; + return this.directoryStructureHost.useCaseSensitiveFileNames; } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return this.partialSystem.readDirectory(path, extensions, exclude, include, depth); + return this.directoryStructureHost.readDirectory(path, extensions, exclude, include, depth); } readFile(fileName: string): string | undefined { - return this.partialSystem.readFile(fileName); + return this.directoryStructureHost.readFile(fileName); } fileExists(file: string): boolean { // As an optimization, don't hit the disks for files we already know don't exist // (because we're watching for their creation). const path = this.toPath(file); - return !this.isWatchedMissingFile(path) && this.partialSystem.fileExists(file); + return !this.isWatchedMissingFile(path) && this.directoryStructureHost.fileExists(file); } resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[] { @@ -334,11 +334,11 @@ namespace ts.server { } directoryExists(path: string): boolean { - return this.partialSystem.directoryExists(path); + return this.directoryStructureHost.directoryExists(path); } getDirectories(path: string): string[] { - return this.partialSystem.getDirectories(path); + return this.directoryStructureHost.getDirectories(path); } /*@internal*/ @@ -518,7 +518,7 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; - this.partialSystem = undefined; + this.directoryStructureHost = undefined; // Clean up file watchers waiting for missing files if (this.missingFilesMap) { @@ -835,7 +835,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.partialSystem); + const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(inserted, this.directoryStructureHost); scriptInfo.attachToProject(this); }, removed => this.detachScriptInfoFromProject(removed) @@ -859,7 +859,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); + (this.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { @@ -882,7 +882,7 @@ namespace ts.server { getScriptInfoForNormalizedPath(fileName: NormalizedPath) { const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClientForNormalizedPath( - fileName, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.partialSystem + fileName, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.directoryStructureHost ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); @@ -1134,8 +1134,8 @@ namespace ts.server { compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, - cachedPartialSystem: PartialSystem) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedPartialSystem); + cachedDirectoryStructureHost: CachedDirectoryStructureHost) { + super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedDirectoryStructureHost); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); this.resolutionCache.setRootDirectory(getDirectoryPath(configFileName)); @@ -1155,8 +1155,8 @@ namespace ts.server { } /*@internal*/ - getCachedPartialSystem() { - return this.partialSystem as CachedPartialSystem; + getCachedDirectoryStructureHost() { + return this.directoryStructureHost as CachedDirectoryStructureHost; } getConfigFilePath() { @@ -1342,7 +1342,7 @@ namespace ts.server { } getEffectiveTypeRoots() { - return getEffectiveTypeRoots(this.getCompilationSettings(), this.partialSystem) || []; + return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || []; } /*@internal*/ diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index e1e05b36f2d..3971b29fe7e 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -292,7 +292,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.partialSystem as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + (p.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways From 68d360585a6b09d93b39774fa54dc3622caee88f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 13:34:56 -0700 Subject: [PATCH 123/144] PR feedback --- src/compiler/builder.ts | 49 +++-- src/compiler/commandLineParser.ts | 2 +- src/compiler/core.ts | 22 +-- src/compiler/program.ts | 84 +-------- src/compiler/resolutionCache.ts | 167 +++++++++++------- src/compiler/sys.ts | 29 ++- src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 72 +------- src/compiler/watch.ts | 79 +++------ src/compiler/watchUtilities.ts | 136 ++++++++++++++ .../unittests/reuseProgramStructure.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 6 +- src/harness/virtualFileSystemWithWatch.ts | 78 ++++---- src/server/editorServices.ts | 32 ++-- src/server/project.ts | 102 ++++++----- src/server/scriptInfo.ts | 23 ++- src/services/jsTyping.ts | 2 +- src/services/services.ts | 8 +- src/services/types.ts | 3 +- 20 files changed, 472 insertions(+), 431 deletions(-) create mode 100644 src/compiler/watchUtilities.ts diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index e7bcb28d820..9436be6e8a1 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -43,10 +43,23 @@ namespace ts { } interface EmitHandler { - addScriptInfo(program: Program, sourceFile: SourceFile): void; - removeScriptInfo(path: Path): void; - updateScriptInfo(program: Program, sourceFile: SourceFile): void; - updaterScriptInfoWithSameVersion(program: Program, sourceFile: SourceFile): boolean; + /** + * Called when sourceFile is added to the program + */ + onAddSourceFile(program: Program, sourceFile: SourceFile): void; + /** + * Called when sourceFile is removed from the program + */ + onRemoveSourceFile(path: Path): void; + /** + * Called when sourceFile is changed + */ + onUpdateSourceFile(program: Program, sourceFile: SourceFile): void; + /** + * Called when source file has not changed but has some of the resolutions invalidated + * If returned true, builder will mark the file as changed (noting that something associated with file has changed) + */ + onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean; /** * Gets the files affected by the script info which has updated shape from the known one */ @@ -135,23 +148,23 @@ namespace ts { function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { registerChangedFile(sourceFile.path, sourceFile.fileName); - emitHandler.addScriptInfo(program, sourceFile); + emitHandler.onAddSourceFile(program, sourceFile); return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined }; } function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) { registerChangedFile(path, existingFileInfo.fileName); - emitHandler.removeScriptInfo(path); + emitHandler.onRemoveSourceFile(path); } function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { if (existingInfo.version !== sourceFile.version) { registerChangedFile(sourceFile.path, sourceFile.fileName); existingInfo.version = sourceFile.version; - emitHandler.updateScriptInfo(program, sourceFile); + emitHandler.onUpdateSourceFile(program, sourceFile); } else if (hasInvalidatedResolution(sourceFile.path) && - emitHandler.updaterScriptInfoWithSameVersion(program, sourceFile)) { + emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) { registerChangedFile(sourceFile.path, sourceFile.fileName); } } @@ -388,10 +401,10 @@ namespace ts { function getNonModuleEmitHandler(): EmitHandler { return { - addScriptInfo: noop, - removeScriptInfo: noop, - updateScriptInfo: noop, - updaterScriptInfoWithSameVersion: returnFalse, + onAddSourceFile: noop, + onRemoveSourceFile: noop, + onUpdateSourceFile: noop, + onUpdateSourceFileWithSameVersion: returnFalse, getFilesAffectedByUpdatedShape }; @@ -409,17 +422,17 @@ namespace ts { function getModuleEmitHandler(): EmitHandler { const references = createMap>(); return { - addScriptInfo: setReferences, - removeScriptInfo, - updateScriptInfo: updateReferences, - updaterScriptInfoWithSameVersion: updateReferencesTrackingChangedReferences, + onAddSourceFile: setReferences, + onRemoveSourceFile, + onUpdateSourceFile: updateReferences, + onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences, getFilesAffectedByUpdatedShape }; function setReferences(program: Program, sourceFile: SourceFile) { const newReferences = getReferencedFiles(program, sourceFile); if (newReferences) { - references.set(sourceFile.path, getReferencedFiles(program, sourceFile)); + references.set(sourceFile.path, newReferences); } } @@ -452,7 +465,7 @@ namespace ts { !!oldReferences.size; } - function removeScriptInfo(removedFilePath: Path) { + function onRemoveSourceFile(removedFilePath: Path) { // Remove existing references references.forEach((referencesInFile, filePath) => { if (referencesInFile.has(removedFilePath)) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d49d993bce1..fae45709a3c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1683,7 +1683,7 @@ namespace ts { return undefined; } let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); - if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { extendedConfigPath = `${extendedConfigPath}.json` as Path; if (!host.fileExists(extendedConfigPath)) { errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9579dec51cd..8d5883b7755 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2262,7 +2262,7 @@ namespace ts { return ScriptKind.TS; case Extension.Tsx: return ScriptKind.TSX; - case ".json": + case Extension.Json: return ScriptKind.JSON; default: return ScriptKind.Unknown; @@ -2669,7 +2669,7 @@ namespace ts { export function assertTypeIsNever(_: never): void { } export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; clearCache(): void; } @@ -2695,7 +2695,7 @@ namespace ts { getCurrentDirectory, getDirectories, readDirectory, - addOrDeleteFileOrFolder, + addOrDeleteFileOrDirectory, addOrDeleteFile, clearCache, exit: code => host.exit(code) @@ -2824,23 +2824,23 @@ namespace ts { } } - function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) { - const existingResult = getCachedFileSystemEntries(fileOrFolderPath); + function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); if (existingResult) { // This was a folder already present, remove it if this doesnt exist any more - if (!host.directoryExists(fileOrFolder)) { - cachedReadDirectoryResult.delete(fileOrFolderPath); + if (!host.directoryExists(fileOrDirectory)) { + cachedReadDirectoryResult.delete(fileOrDirectoryPath); } } else { // This was earlier a file (hence not in cached directory contents) // or we never cached the directory containing it - const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath); + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); if (parentResult) { - const baseName = getBaseNameOfFileName(fileOrFolder); + const baseName = getBaseNameOfFileName(fileOrDirectory); if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath)); - updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath)); + updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath)); + updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath)); } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 01ec2e62b2e..674d0b6cfac 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -395,6 +395,9 @@ namespace ts { allDiagnostics?: Diagnostic[]; } + /** + * Determines if program structure is upto date or needs to be recreated + */ export function isProgramUptoDate( program: Program | undefined, rootFileNames: string[], @@ -402,10 +405,10 @@ namespace ts { getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames: () => boolean, + hasChangedAutomaticTypeDirectiveNames: boolean, ): boolean { // If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date - if (!program || hasChangedAutomaticTypeDirectiveNames()) { + if (!program || hasChangedAutomaticTypeDirectiveNames) { return false; } @@ -416,7 +419,7 @@ namespace ts { // If any file is not up-to-date, then the whole program is not up-to-date if (program.getSourceFiles().some(sourceFileNotUptoDate)) { - return false; + return false; } // If any of the missing file paths are now created @@ -464,78 +467,6 @@ namespace ts { ); } - /** - * Updates the existing missing file watches with the new set of missing files after new program is created - */ - export function updateMissingFilePathsWatch( - program: Program, - missingFileWatches: Map, - createMissingFileWatch: (missingFilePath: Path) => FileWatcher, - ) { - const missingFilePaths = program.getMissingFilePaths(); - const newMissingFilePathMap = arrayToSet(missingFilePaths); - // Update the missing file paths watcher - mutateMap( - missingFileWatches, - newMissingFilePathMap, - { - // Watch the missing files - createNewValue: createMissingFileWatch, - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - onDeleteValue: closeFileWatcher - } - ); - } - - export interface WildcardDirectoryWatcher { - watcher: FileWatcher; - flags: WatchDirectoryFlags; - } - - /** - * Updates the existing wild card directory watches with the new set of wild card directories from the config file - * after new program is created because the config file was reloaded or program was created first time from the config file - * Note that there is no need to call this function when the program is updated with additional files without reloading config files, - * as wildcard directories wont change unless reloading config file - */ - export function updateWatchingWildcardDirectories( - existingWatchedForWildcards: Map, - wildcardDirectories: Map, - watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher - ) { - mutateMap( - existingWatchedForWildcards, - wildcardDirectories, - { - // Create new watch and recursive info - createNewValue: createWildcardDirectoryWatcher, - // Close existing watch thats not needed any more - onDeleteValue: closeFileWatcherOf, - // Close existing watch that doesnt match in the flags - onExistingValue: updateWildcardDirectoryWatcher - } - ); - - function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { - // Create new watch and recursive info - return { - watcher: watchDirectory(directory, flags), - flags - }; - } - - function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { - // Watcher needs to be updated if the recursive flags dont match - if (existingWatcher.flags === flags) { - return; - } - - existingWatcher.watcher.close(); - existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); - } - } - /** * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' * that represent a compilation unit. @@ -599,7 +530,6 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModuleFull[]; const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; - const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile, reusedNames).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. @@ -1105,7 +1035,7 @@ namespace ts { return oldProgram.structureIsReused; } - if (hasChangedAutomaticTypeDirectiveNames()) { + if (host.hasChangedAutomaticTypeDirectiveNames) { return oldProgram.structureIsReused = StructureIsReused.SafeModules; } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 814d5053341..aecc6989891 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -1,5 +1,6 @@ /// /// +/// /*@internal*/ namespace ts { @@ -18,8 +19,6 @@ namespace ts { startCachingPerDirectoryResolution(): void; finishCachingPerDirectoryResolution(): void; - setRootDirectory(dir: string): void; - updateTypeRootsWatch(): void; closeTypeRootsWatch(): void; @@ -35,6 +34,12 @@ namespace ts { resolvedFileName: string | undefined; } + interface ResolvedModuleWithFailedLookupLocations extends ts.ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { + } + + interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { + } + export interface ResolutionCacheHost extends ModuleResolutionHost { toPath(fileName: string): Path; getCompilationSettings(): CompilerOptions; @@ -67,7 +72,7 @@ namespace ts { (resolution: T): R; } - export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { + export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; let allFilesHaveInvalidatedResolution = false; @@ -83,12 +88,18 @@ namespace ts { const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + /** + * These are the extensions that failed lookup files will have by default, + * any other extension of failed lookup will be store that path in custom failed lookup path + * This helps in not having to comb through all resolutions when files are added/removed + * Note that .d.ts file also has .d.ts extension hence will be part of default extensions + */ const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; const customFailedLookupPaths = createMap(); const directoryWatchesOfFailedLookups = createMap(); - let rootDir: string; - let rootPath: Path; + const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); + const rootPath = rootDir && resolutionHost.toPath(rootDir); // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames const typeRootsWatches = createMap(); @@ -103,7 +114,6 @@ namespace ts { removeResolutionsOfFile, invalidateResolutionOfFile, createHasInvalidatedResolution, - setRootDirectory, updateTypeRootsWatch, closeTypeRootsWatch, clear @@ -117,12 +127,6 @@ namespace ts { return resolution.resolvedTypeReferenceDirective; } - function setRootDirectory(dir: string) { - Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size); - rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory())); - rootPath = resolutionHost.toPath(rootDir); - } - function isInDirectoryPath(dir: Path, file: Path) { if (dir === undefined || file.length <= dir.length) { return false; @@ -238,9 +242,17 @@ namespace ts { perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); - const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations); - watchFailedLookupLocationOfResolution(resolution, diffIndex || 0); - stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0); + if (resolution.failedLookupLocations) { + if (existingResolution && existingResolution.failedLookupLocations) { + watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution); + } + else { + watchFailedLookupLocationOfResolution(resolution, 0); + } + } + else if (existingResolution) { + stopWatchFailedLookupLocationOfResolution(existingResolution); + } if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { filesWithChangedSetOfUnresolvedImports.push(path); // reset log changes to avoid recording the same file multiple times @@ -344,68 +356,91 @@ namespace ts { return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); } - function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations, startIndex?: number) { - if (resolution && resolution.failedLookupLocations) { - for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) { - const failedLookupLocation = resolution.failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - // If the failed lookup location path is not one of the supported extensions, - // store it in the custom path - if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { - const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; - customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); - } - const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - if (dirWatcher) { - dirWatcher.refCount++; - } - else { - directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); - } + function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) { + const failedLookupLocations = resolution.failedLookupLocations; + const existingFailedLookupLocations = existingResolution.failedLookupLocations; + for (let index = 0; index < failedLookupLocations.length; index++) { + if (index === existingFailedLookupLocations.length) { + // Additional failed lookup locations, watch from this index + watchFailedLookupLocationOfResolution(resolution, index); + return; + } + else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) { + // Different failed lookup locations, + // Watch new resolution failed lookup locations from this index and + // stop watching existing resolutions from this index + watchFailedLookupLocationOfResolution(resolution, index); + stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index); + return; + } + } + + // All new failed lookup locations are already watched (and are same), + // Stop watching failed lookup locations of existing resolution after failed lookup locations length + stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length); + } + + function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { + for (let i = startIndex; i < failedLookupLocations.length; i++) { + const failedLookupLocation = failedLookupLocations[i]; + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + // If the failed lookup location path is not one of the supported extensions, + // store it in the custom path + if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { + const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; + customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); + } + const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + if (dirWatcher) { + dirWatcher.refCount++; + } + else { + directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 }); } } } function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { - stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + if (resolution.failedLookupLocations) { + stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0); + } } - function stopWatchFailedLookupLocationOfResolutionFrom(resolution: ResolutionWithFailedLookupLocations, startIndex: number) { - if (resolution && resolution.failedLookupLocations) { - for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) { - const failedLookupLocation = resolution.failedLookupLocations[i]; - const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); - const refCount = customFailedLookupPaths.get(failedLookupLocationPath); - if (refCount) { - if (refCount === 1) { - customFailedLookupPaths.delete(failedLookupLocationPath); - } - else { - customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); - } + function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) { + for (let i = startIndex; i < failedLookupLocations.length; i++) { + const failedLookupLocation = failedLookupLocations[i]; + const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); + const refCount = customFailedLookupPaths.get(failedLookupLocationPath); + if (refCount) { + if (refCount === 1) { + customFailedLookupPaths.delete(failedLookupLocationPath); + } + else { + Debug.assert(refCount > 1); + customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); } - const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); - const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); - // Do not close the watcher yet since it might be needed by other failed lookup locations. - dirWatcher.refCount--; } + const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); + // Do not close the watcher yet since it might be needed by other failed lookup locations. + dirWatcher.refCount--; } } function createDirectoryWatcher(directory: string, dirPath: Path) { - return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => { - const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (!allFilesHaveInvalidatedResolution && - dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) { - if (invalidateResolutionOfFailedLookupLocation(fileOrFolderPath, dirPath === fileOrFolderPath)) { + dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath) { + if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) { resolutionHost.onInvalidatedResolution(); } } @@ -482,21 +517,21 @@ namespace ts { ); } - function invalidateResolutionOfFailedLookupLocation(fileOrFolderPath: Path, isCreatingWatchedDirectory: boolean) { + function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { let isChangedFailedLookupLocation: (location: string) => boolean; if (isCreatingWatchedDirectory) { // Watching directory is created // Invalidate any resolution has failed lookup in this directory - isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrFolderPath, resolutionHost.toPath(location)); + isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location)); } else { - // Some file or folder in the watching directory is created + // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path - if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) { + if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { return false; } - // Resolution need to be invalidated if failed lookup location is same as the file or folder getting created - isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath; + // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created + isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath; } const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation); const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size; @@ -513,11 +548,11 @@ namespace ts { function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher { // Create new watch and recursive info - return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => { - const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { + const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); if (resolutionHost.getCachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 822de1eef52..fa3ec22f586 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -78,8 +78,7 @@ namespace ts { close(): void; } - export interface DirectoryWatcher extends FileWatcher { - directoryName: string; + interface DirectoryWatcher extends FileWatcher { referenceCount: number; } @@ -187,7 +186,7 @@ namespace ts { fileWatcherCallbacks.remove(filePath, callback); } - function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) { + function fileEventHandler(eventName: string, relativeFileName: string | undefined, baseDirPath: string) { // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" const fileName = !isString(relativeFileName) ? undefined @@ -255,19 +254,25 @@ namespace ts { } function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher { - // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows - // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) let options: any; + /** Watcher for the directory depending on whether it is missing or present */ let watcher = !directoryExists(directoryName) ? watchMissingDirectory() : watchPresentDirectory(); return { close: () => { + // Close the watcher (either existing directory watcher or missing directory watcher) watcher.close(); } }; + /** + * Watch the directory that is currently present + * and when the watched directory is deleted, switch to missing directory watcher + */ function watchPresentDirectory(): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) if (options === undefined) { if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { options = { persistent: true, recursive: !!recursive }; @@ -283,14 +288,20 @@ namespace ts { callback ); dirWatcher.on("error", () => { - // Deleting file - watcher = watchMissingDirectory(); - // Call the callback for current directory - callback("rename", ""); + if (!directoryExists(directoryName)) { + // Deleting directory + watcher = watchMissingDirectory(); + // Call the callback for current directory + callback("rename", ""); + } }); return dirWatcher; } + /** + * Watch the directory that is missing + * and switch to existing directory when the directory is created + */ function watchMissingDirectory(): FileWatcher { return fsWatchFile(directoryName, (_fileName, eventKind) => { if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) { diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index dee33cbf163..07f69ddfe28 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -36,6 +36,7 @@ "sourcemap.ts", "declarationEmitter.ts", "emitter.ts", + "watchUtilities.ts", "program.ts", "builder.ts", "resolutionCache.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2d955da1252..e62981866b3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4099,8 +4099,6 @@ namespace ts { readonly resolvedModule: ResolvedModuleFull | undefined; /* @internal */ readonly failedLookupLocations: ReadonlyArray; - /*@internal*/ - isInvalidated?: boolean; } export interface ResolvedTypeReferenceDirective { @@ -4114,8 +4112,6 @@ namespace ts { export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; readonly failedLookupLocations: ReadonlyArray; - /*@internal*/ - isInvalidated?: boolean; } export interface HasInvalidatedResolution { @@ -4150,7 +4146,7 @@ namespace ts { getEnvironmentVariable?(name: string): string; onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; hasInvalidatedResolution?: HasInvalidatedResolution; - hasChangedAutomaticTypeDirectiveNames?(): boolean; + hasChangedAutomaticTypeDirectiveNames?: boolean; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 87bc601bd7e..fcd390cded6 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3521,7 +3521,7 @@ namespace ts { /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ - export function clearMap(map: Map, onDeleteValue: (existingValue: T, key: string) => void) { + export function clearMap(map: Map, onDeleteValue: (valueInMap: T, key: string) => void) { // Remove all map.forEach(onDeleteValue); map.clear(); @@ -3568,76 +3568,6 @@ namespace ts { }); } - /** - * Find the index where arrayA and arrayB differ - */ - export function findDiffIndex(arrayA: ReadonlyArray, arrayB: ReadonlyArray) { - for (let i = 0; i < arrayA.length; i++) { - if (i === arrayB.length || arrayA[i] !== arrayB[i]) { - return i; - } - } - return arrayA.length; - } - - export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { - return host.watchFile(file, cb); - } - - export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { - const watcherCaption = `FileWatcher:: `; - return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb); - } - - export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; - export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { - return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); - } - - export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { - const watcherCaption = `FileWatcher:: `; - return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path); - } - - export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { - const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - return host.watchDirectory(directory, cb, recursive); - } - - export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { - const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; - return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags); - } - - type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; - type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; - function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { - const info = `PathInfo: ${file}`; - log(`${watcherCaption}Added: ${info}`); - const watcher = addWatch(host, file, (fileName, cbOptional1?) => { - const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : ""; - log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`); - const start = timestamp(); - cb(fileName, cbOptional1, optional); - const elapsed = timestamp() - start; - log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`); - }, optional); - return { - close: () => { - log(`${watcherCaption}Close: ${info}`); - watcher.close(); - } - }; - } - - export function closeFileWatcher(watcher: FileWatcher) { - watcher.close(); - } - - export function closeFileWatcherOf(objWithWatcher: T) { - objWithWatcher.watcher.close(); - } - /** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */ export function forEachAncestorDirectory(directory: string, callback: (directory: string) => T): T { while (true) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 2d1d6e8bf58..8b098f41098 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -245,7 +245,7 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations - let changedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed + let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop; @@ -269,26 +269,25 @@ namespace ts { const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost - getSourceFile: getVersionedSourceFile, + getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile, - trace, - directoryExists, + readFile: fileName => system.readFile(fileName), + trace: s => system.write(s + newLine), + directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName), getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories, + getDirectories: path => directoryStructureHost.getDirectories(path), realpath, - resolveTypeReferenceDirectives, - resolveModuleNames, + resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), + resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false), onReleaseOldSourceFile, - hasChangedAutomaticTypeDirectiveNames, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, @@ -296,12 +295,14 @@ namespace ts { watchTypeRootsDirectory: watchDirectory, getCachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, - onChangedAutomaticTypeDirectiveNames, + onChangedAutomaticTypeDirectiveNames: () => { + hasChangedAutomaticTypeDirectiveNames = true; + scheduleProgramUpdate(); + }, writeLog }; // Cache for the module resolution - const resolutionCache = createResolutionCache(compilerHost); - resolutionCache.setRootDirectory(configFileName ? + const resolutionCache = createResolutionCache(compilerHost, configFileName ? getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : getCurrentDirectory() ); @@ -337,6 +338,7 @@ namespace ts { // Compile the program resolutionCache.startCachingPerDirectoryResolution(); compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; + compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); resolutionCache.finishCachingPerDirectoryResolution(); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); @@ -379,38 +381,10 @@ namespace ts { return directoryStructureHost.fileExists(fileName); } - function directoryExists(directoryName: string) { - return directoryStructureHost.directoryExists(directoryName); - } - - function readFile(fileName: string) { - return system.readFile(fileName); - } - - function trace(s: string) { - return system.write(s + newLine); - } - - function getDirectories(path: string) { - return directoryStructureHost.getDirectories(path); - } - - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]) { - return resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); - } - - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { - return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); - } - function getDefaultLibLocation(): string { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } - function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { - return getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile); - } - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { const hostSourceFile = sourceFilesCache.get(path); // No source file on the host @@ -594,15 +568,6 @@ namespace ts { return watchDirectoryWorker(system, directory, cb, flags, writeLog); } - function onChangedAutomaticTypeDirectiveNames() { - changedAutomaticTypeDirectiveNames = true; - scheduleProgramUpdate(); - } - - function hasChangedAutomaticTypeDirectiveNames() { - return changedAutomaticTypeDirectiveNames; - } - function watchMissingFilePath(missingFilePath: Path) { return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } @@ -633,19 +598,19 @@ namespace ts { function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { return watchDirectory( directory, - fileOrFolder => { + fileOrDirectory => { Debug.assert(!!configFileName); - const fileOrFolderPath = toPath(fileOrFolder); + const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - removeSourceFile(fileOrFolderPath); + (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + removeSourceFile(fileOrDirectoryPath); - // If the the added or created file or folder is not supported file name, ignore the file + // If the the added or created file or directory is not supported file name, ignore the file // But when watched directory is added/removed, we need to reload the file list - if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); + if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); return; } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts new file mode 100644 index 00000000000..de415293954 --- /dev/null +++ b/src/compiler/watchUtilities.ts @@ -0,0 +1,136 @@ +/// + +namespace ts { + /** + * Updates the existing missing file watches with the new set of missing files after new program is created + */ + export function updateMissingFilePathsWatch( + program: Program, + missingFileWatches: Map, + createMissingFileWatch: (missingFilePath: Path) => FileWatcher, + ) { + const missingFilePaths = program.getMissingFilePaths(); + const newMissingFilePathMap = arrayToSet(missingFilePaths); + // Update the missing file paths watcher + mutateMap( + missingFileWatches, + newMissingFilePathMap, + { + // Watch the missing files + createNewValue: createMissingFileWatch, + // Files that are no longer missing (e.g. because they are no longer required) + // should no longer be watched. + onDeleteValue: closeFileWatcher + } + ); + } + + export interface WildcardDirectoryWatcher { + watcher: FileWatcher; + flags: WatchDirectoryFlags; + } + + /** + * Updates the existing wild card directory watches with the new set of wild card directories from the config file + * after new program is created because the config file was reloaded or program was created first time from the config file + * Note that there is no need to call this function when the program is updated with additional files without reloading config files, + * as wildcard directories wont change unless reloading config file + */ + export function updateWatchingWildcardDirectories( + existingWatchedForWildcards: Map, + wildcardDirectories: Map, + watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher + ) { + mutateMap( + existingWatchedForWildcards, + wildcardDirectories, + { + // Create new watch and recursive info + createNewValue: createWildcardDirectoryWatcher, + // Close existing watch thats not needed any more + onDeleteValue: closeFileWatcherOf, + // Close existing watch that doesnt match in the flags + onExistingValue: updateWildcardDirectoryWatcher + } + ); + + function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { + // Create new watch and recursive info + return { + watcher: watchDirectory(directory, flags), + flags + }; + } + + function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { + // Watcher needs to be updated if the recursive flags dont match + if (existingWatcher.flags === flags) { + return; + } + + existingWatcher.watcher.close(); + existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); + } + } +} + +/* @internal */ +namespace ts { + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { + return host.watchFile(file, cb); + } + + export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb); + } + + export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; + export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { + return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); + } + + export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path); + } + + export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { + const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; + return host.watchDirectory(directory, cb, recursive); + } + + export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; + return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags); + } + + type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; + type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { + const info = `PathInfo: ${file}`; + log(`${watcherCaption}Added: ${info}`); + const watcher = addWatch(host, file, (fileName, cbOptional1?) => { + const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : ""; + log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`); + const start = timestamp(); + cb(fileName, cbOptional1, optional); + const elapsed = timestamp() - start; + log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`); + }, optional); + return { + close: () => { + log(`${watcherCaption}Close: ${info}`); + watcher.close(); + } + }; + } + + export function closeFileWatcher(watcher: FileWatcher) { + watcher.close(); + } + + export function closeFileWatcherOf(objWithWatcher: T) { + objWithWatcher.watcher.close(); + } +} diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 53a26c2ae95..5c2b9edd9d0 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -885,7 +885,7 @@ namespace ts { program, newRootFileNames, newOptions, path => program.getSourceFileByPath(path).version, /*fileExists*/ returnFalse, /*hasInvalidatedResolution*/ returnFalse, - /*hasChangedAutomaticTypeDirectiveNames*/ returnFalse + /*hasChangedAutomaticTypeDirectiveNames*/ false ); assert.isTrue(actual); } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index cac52d34a7e..7e1205f76cf 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -967,7 +967,7 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); }); - it("should resuse same project if file is opened from the configured project that has no open files", () => { + it("should reuse same project if file is opened from the configured project that has no open files", () => { const file1 = { path: "/a/b/main.ts", content: "let x =1;" @@ -4639,7 +4639,7 @@ namespace ts.projectSystem { verifyProjectAndWatchedDirectories(); callsTrackingHost.verifyNoHostCalls(); - function getFilePathIfOpen(f: FileOrFolder) { + function getFilePathIfNotOpen(f: FileOrFolder) { const path = toCanonical(f.path); const info = projectService.getScriptInfoForPath(toCanonical(f.path)); return info && info.isScriptOpen() ? undefined : path; @@ -4647,7 +4647,7 @@ namespace ts.projectSystem { function verifyProjectAndWatchedDirectories() { checkProjectActualFiles(project, map(projectFiles, f => f.path)); - checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen)); checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true); checkWatchedDirectories(host, [], /*recursive*/ false); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index e1e0e6f4c7e..c16f57235e4 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -246,17 +246,17 @@ namespace ts.TestFSWithWatch { const mapNewLeaves = createMap(); const isNewFs = this.fs.size === 0; // always inject safelist file in the list of files - for (const fileOrFolder of fileOrFolderList.concat(this.withSafeList ? safeList : [])) { - const path = this.toFullPath(fileOrFolder.path); + for (const fileOrDirectory of fileOrFolderList.concat(this.withSafeList ? safeList : [])) { + const path = this.toFullPath(fileOrDirectory.path); mapNewLeaves.set(path, true); // If its a change const currentEntry = this.fs.get(path); if (currentEntry) { if (isFile(currentEntry)) { - if (isString(fileOrFolder.content)) { + if (isString(fileOrDirectory.content)) { // Update file - if (currentEntry.content !== fileOrFolder.content) { - currentEntry.content = fileOrFolder.content; + if (currentEntry.content !== fileOrDirectory.content) { + currentEntry.content = fileOrDirectory.content; this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed); } } @@ -266,7 +266,7 @@ namespace ts.TestFSWithWatch { } else { // Folder - if (isString(fileOrFolder.content)) { + if (isString(fileOrDirectory.content)) { // TODO: Changing from folder => file } else { @@ -275,32 +275,32 @@ namespace ts.TestFSWithWatch { } } else { - this.ensureFileOrFolder(fileOrFolder); + this.ensureFileOrFolder(fileOrDirectory); } } if (!isNewFs) { - this.fs.forEach((fileOrFolder, path) => { + this.fs.forEach((fileOrDirectory, path) => { // If this entry is not from the new file or folder if (!mapNewLeaves.get(path)) { // Leaf entries that arent in new list => remove these - if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) { - this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path)); + if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { + this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path)); } } }); } } - ensureFileOrFolder(fileOrFolder: FileOrFolder) { - if (isString(fileOrFolder.content)) { - const file = this.toFile(fileOrFolder); + ensureFileOrFolder(fileOrDirectory: FileOrFolder) { + if (isString(fileOrDirectory.content)) { + const file = this.toFile(fileOrDirectory); Debug.assert(!this.fs.get(file.path)); const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); this.addFileOrFolderInFolder(baseFolder, file); } else { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); this.ensureFolder(fullPath); } } @@ -326,44 +326,44 @@ namespace ts.TestFSWithWatch { return folder; } - private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) { - folder.entries.push(fileOrFolder); - this.fs.set(fileOrFolder.path, fileOrFolder); + private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder) { + folder.entries.push(fileOrDirectory); + this.fs.set(fileOrDirectory.path, fileOrDirectory); - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created); + if (isFile(fileOrDirectory)) { + this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created); } - this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath); + this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath); } - private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { - const basePath = getDirectoryPath(fileOrFolder.path); + private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) { + const basePath = getDirectoryPath(fileOrDirectory.path); const baseFolder = this.fs.get(basePath) as Folder; - if (basePath !== fileOrFolder.path) { + if (basePath !== fileOrDirectory.path) { Debug.assert(!!baseFolder); - filterMutate(baseFolder.entries, entry => entry !== fileOrFolder); + filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory); } - this.fs.delete(fileOrFolder.path); + this.fs.delete(fileOrDirectory.path); - if (isFile(fileOrFolder)) { - this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted); + if (isFile(fileOrDirectory)) { + this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); } else { - Debug.assert(fileOrFolder.entries.length === 0); - const relativePath = this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath); + Debug.assert(fileOrDirectory.entries.length === 0); + const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath); // Invoke directory and recursive directory watcher for the folder // Here we arent invoking recursive directory watchers for the base folders // since that is something we would want to do for both file as well as folder we are deleting - invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath)); } - if (basePath !== fileOrFolder.path) { + if (basePath !== fileOrDirectory.path) { if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) { this.removeFileOrFolder(baseFolder, isRemovableLeafFolder); } else { - this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath); + this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath); } } } @@ -402,13 +402,13 @@ namespace ts.TestFSWithWatch { } } - private toFile(fileOrFolder: FileOrFolder): File { - const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory); + private toFile(fileOrDirectory: FileOrFolder): File { + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); return { path: this.toPath(fullPath), - content: fileOrFolder.content, + content: fileOrDirectory.content, fullPath, - fileSize: fileOrFolder.fileSize + fileSize: fileOrDirectory.fileSize }; } @@ -477,7 +477,7 @@ namespace ts.TestFSWithWatch { }); } - watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { + watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher { const path = this.toFullPath(directoryName); const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; const callback: TestDirectoryWatcher = { @@ -486,8 +486,6 @@ namespace ts.TestFSWithWatch { }; map.add(path, callback); return { - referenceCount: 0, - directoryName, close: () => map.remove(path, callback) }; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index c93098c32c2..25a285929be 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -349,7 +349,7 @@ namespace ts.server { private readonly projectToSizeMap: Map = createMap(); /** * This is a map of config file paths existance that doesnt need query to disk - * - The entry can be present because there is inferred project that needs to watch addition of config file to folder + * - The entry can be present because there is inferred project that needs to watch addition of config file to directory * In this case the exists could be true/false based on config file is present or not * - Or it is present if we have configured project open with config file at that location * In this case the exists property is always true @@ -753,15 +753,15 @@ namespace ts.server { return this.watchDirectory( this.host, directory, - fileOrFolder => { - const fileOrFolderPath = this.toPath(fileOrFolder); - project.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + fileOrDirectory => { + const fileOrDirectoryPath = this.toPath(fileOrDirectory); + project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); const configFilename = project.getConfigFilePath(); - // If the the added or created file or folder is not supported file name, ignore the file + // If the the added or created file or directory is not supported file name, ignore the file // But when watched directory is added/removed, we need to reload the file list - if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { - this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); + if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`); return; } @@ -833,7 +833,7 @@ namespace ts.server { switch (project.projectKind) { case ProjectKind.External: unorderedRemoveItem(this.externalProjects, project); - this.projectToSizeMap.delete((project as ExternalProject).externalProjectName); + this.projectToSizeMap.delete(project.getProjectName()); break; case ProjectKind.Configured: this.configuredProjects.delete((project).canonicalConfigFilePath); @@ -852,7 +852,7 @@ namespace ts.server { const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) || this.getOrCreateSingleInferredProjectIfEnabled() || - this.createInferredProject(); + this.createInferredProject(getDirectoryPath(info.path)); project.addRoot(info); project.updateGraph(); @@ -1374,7 +1374,7 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition); this.externalProjects.push(project); - this.sendProjectTelemetry(project.externalProjectName, project); + this.sendProjectTelemetry(projectFileName, project); return project; } @@ -1461,7 +1461,7 @@ namespace ts.server { this.addFilesToNonInferredProjectAndUpdateGraph(project, filesToAdd, fileNamePropertyReader, projectOptions.typeAcquisition); this.configuredProjects.set(project.canonicalConfigFilePath, project); this.setConfigFileExistenceByNewConfiguredProject(project); - this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); + this.sendProjectTelemetry(configFileName, project, projectOptions); return project; } @@ -1584,7 +1584,7 @@ namespace ts.server { return project; } } - return this.createInferredProject(/*isSingleInferredProject*/ false, projectRootPath); + return this.createInferredProject(projectRootPath, /*isSingleInferredProject*/ false, projectRootPath); } // we don't have an explicit root path, so we should try to find an inferred project @@ -1621,12 +1621,12 @@ namespace ts.server { return this.inferredProjects[0]; } - return this.createInferredProject(/*isSingleInferredProject*/ true); + return this.createInferredProject(/*rootDirectoryForResolution*/ undefined, /*isSingleInferredProject*/ true); } - private createInferredProject(isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { + private createInferredProject(rootDirectoryForResolution: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects; - const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath); + const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, rootDirectoryForResolution); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } @@ -1669,10 +1669,12 @@ namespace ts.server { } } + /*@internal*/ getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } + /*@internal*/ getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } diff --git a/src/server/project.ts b/src/server/project.ts index 746e6bf3fbe..76d4499f688 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -162,7 +162,8 @@ namespace ts.server { */ private projectStateVersion = 0; - private changedAutomaticTypeDirectiveNames = false; + /*@internal*/ + hasChangedAutomaticTypeDirectiveNames = false; private typingFiles: SortedReadonlyArray; @@ -201,7 +202,8 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - /*@internal*/public directoryStructureHost: DirectoryStructureHost) { + /*@internal*/public directoryStructureHost: DirectoryStructureHost, + rootDirectoryForResolution: string | undefined) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -224,7 +226,7 @@ namespace ts.server { } this.languageService = createLanguageService(this, this.documentRegistry); - this.resolutionCache = createResolutionCache(this); + this.resolutionCache = createResolutionCache(this, rootDirectoryForResolution); if (!languageServiceEnabled) { this.disableLanguageService(); } @@ -244,29 +246,26 @@ namespace ts.server { } getScriptFileNames() { - const result: string[] = []; - if (this.rootFiles) { - this.rootFilesMap.forEach((value, _path) => { - const f: ScriptInfo = isScriptInfo(value) && value; - if (this.languageServiceEnabled || (f && f.isScriptOpen())) { - // if language service is disabled - process only files that are open - result.push(f ? f.fileName : value as NormalizedPath); - } - }); - if (this.typingFiles) { - for (const f of this.typingFiles) { - result.push(f); - } - } + if (!this.rootFiles) { + return ts.emptyArray; } - return result; + + let result: string[] | undefined; + this.rootFilesMap.forEach(value => { + if (this.languageServiceEnabled || (isScriptInfo(value) && value.isScriptOpen())) { + // if language service is disabled - process only files that are open + (result || (result = [])).push(isScriptInfo(value) ? value.fileName : value); + } + }); + + return addRange(result, this.typingFiles) || ts.emptyArray; } - private getScriptInfoLSHost(fileName: string) { + private getOrCreateScriptInfoAndAttachToProject(fileName: string) { const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost); if (scriptInfo) { const existingValue = this.rootFilesMap.get(scriptInfo.path); - if (existingValue !== undefined && existingValue !== scriptInfo) { + if (existingValue !== scriptInfo && existingValue !== undefined) { // This was missing path earlier but now the file exists. Update the root this.rootFiles.push(scriptInfo); this.rootFilesMap.set(scriptInfo.path, scriptInfo); @@ -277,17 +276,17 @@ namespace ts.server { } getScriptKind(fileName: string) { - const info = this.getScriptInfoLSHost(fileName); + const info = this.getOrCreateScriptInfoAndAttachToProject(fileName); return info && info.scriptKind; } getScriptVersion(filename: string) { - const info = this.getScriptInfoLSHost(filename); + const info = this.getOrCreateScriptInfoAndAttachToProject(filename); return info && info.getLatestVersion(); } getScriptSnapshot(filename: string): IScriptSnapshot { - const scriptInfo = this.getScriptInfoLSHost(filename); + const scriptInfo = this.getOrCreateScriptInfoAndAttachToProject(filename); if (scriptInfo) { return scriptInfo.getSnapshot(); } @@ -377,15 +376,10 @@ namespace ts.server { /*@internal*/ onChangedAutomaticTypeDirectiveNames() { - this.changedAutomaticTypeDirectiveNames = true; + this.hasChangedAutomaticTypeDirectiveNames = true; this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - /*@internal*/ - hasChangedAutomaticTypeDirectiveNames() { - return this.changedAutomaticTypeDirectiveNames; - } - /*@internal*/ getGlobalCache() { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; @@ -802,7 +796,7 @@ namespace ts.server { // - oldProgram is not set - this is a first time updateGraph is called // - newProgram is different from the old program and structure of the old program was not reused. const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)); - this.changedAutomaticTypeDirectiveNames = false; + this.hasChangedAutomaticTypeDirectiveNames = false; if (hasChanges) { if (oldProgram) { for (const f of oldProgram.getSourceFiles()) { @@ -1035,7 +1029,12 @@ namespace ts.server { super.setCompilerOptions(newOptions); } - constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, public readonly projectRootPath?: string | undefined) { + constructor( + projectService: ProjectService, + documentRegistry: DocumentRegistry, + compilerOptions: CompilerOptions, + public readonly projectRootPath: string | undefined, + rootDirectoryForResolution: string | undefined) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -1044,7 +1043,8 @@ namespace ts.server { /*languageServiceEnabled*/ true, compilerOptions, /*compileOnSaveEnabled*/ false, - projectService.host); + projectService.host, + rootDirectoryForResolution); } addRoot(info: ScriptInfo) { @@ -1053,13 +1053,6 @@ namespace ts.server { this.toggleJsInferredProject(/*isJsInferredProject*/ true); } super.addRoot(info); - // For first root set the resolution cache root dir - if (this.getRootFiles.length === 1) { - const rootDirPath = this.getProjectRootPath(); - if (rootDirPath) { - this.resolutionCache.setRootDirectory(rootDirPath); - } - } } removeRoot(info: ScriptInfo) { @@ -1081,11 +1074,9 @@ namespace ts.server { } getProjectRootPath() { - // Single inferred project does not have a project root. - if (this.projectService.useSingleInferredProject) { - return undefined; - } - return this.projectRootPath || getDirectoryPath(this.getRootFiles()[0]); + return this.projectRootPath || + // Single inferred project does not have a project root. + !this.projectService.useSingleInferredProject && getDirectoryPath(this.getRootFiles()[0]); } close() { @@ -1135,10 +1126,18 @@ namespace ts.server { languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, cachedDirectoryStructureHost: CachedDirectoryStructureHost) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedDirectoryStructureHost); + super(configFileName, + ProjectKind.Configured, + projectService, + documentRegistry, + hasExplicitListOfFiles, + languageServiceEnabled, + compilerOptions, + compileOnSaveEnabled, + cachedDirectoryStructureHost, + getDirectoryPath(configFileName)); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); - this.resolutionCache.setRootDirectory(getDirectoryPath(configFileName)); } /** @@ -1370,8 +1369,15 @@ namespace ts.server { languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, private readonly projectFilePath?: string) { - super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host); - this.resolutionCache.setRootDirectory(this.getProjectRootPath()); + super(externalProjectName, + ProjectKind.External, + projectService, + documentRegistry, + /*hasExplicitListOfFiles*/ true, + languageServiceEnabled, compilerOptions, + compileOnSaveEnabled, + projectService.host, + getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); } getExcludedFiles() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 3971b29fe7e..245b27dd58a 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -4,17 +4,36 @@ namespace ts.server { /* @internal */ export class TextStorage { - // Generated only on demand and removes the text if any edits + /** + * Generated only on demand (based on edits, or information requested) + * The property text is set to undefined when edits happen on the cache + */ private svc: ScriptVersionCache | undefined; private svcVersion = 0; - // Store text when there is no svc or svc has no change, on edit, remove the text + /** + * Stores the text when there are no changes to the script version cache + * The script version cache is generated on demand and text is still retained. + * Only on edits to the script version cache, the text will be set to undefined + */ private text: string; + /** + * Line map for the text when there is no script version cache present + */ private lineMap: number[]; private textVersion = 0; + /** + * True if the text is for the file thats open in the editor + */ public isOpen: boolean; + /** + * True if the text present is the text from the file on the disk + */ private ownFileText: boolean; + /** + * True when reloading contents of file from the disk is pending + */ private pendingReloadFromDisk: boolean; constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) { diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index c775a995fd6..f1859a90b98 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -207,7 +207,7 @@ namespace ts.JsTyping { } // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar` - const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); + const fileNames = host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`); const packageNames: string[] = []; for (const fileName of fileNames) { diff --git a/src/services/services.ts b/src/services/services.ts index d91ebbd95ec..5057ced0c90 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1101,13 +1101,11 @@ namespace ts { } function synchronizeHostData(): void { - const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse; - // perform fast check if host supports it if (host.getProjectVersion) { const hostProjectVersion = host.getProjectVersion(); if (hostProjectVersion) { - if (lastProjectVersion === hostProjectVersion && !hasChangedAutomaticTypeDirectiveNames()) { + if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames) { return; } @@ -1129,7 +1127,7 @@ namespace ts { const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; // If the program is already up-to-date, we can reuse it - if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { + if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, host.hasChangedAutomaticTypeDirectiveNames)) { return; } @@ -1170,7 +1168,7 @@ namespace ts { }, onReleaseOldSourceFile, hasInvalidatedResolution, - hasChangedAutomaticTypeDirectiveNames + hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames }; if (host.trace) { compilerHost.trace = message => host.trace(message); diff --git a/src/services/types.ts b/src/services/types.ts index bd1358b09c1..4c77cd14d9c 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -186,7 +186,7 @@ namespace ts { resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; hasInvalidatedResolution?: HasInvalidatedResolution; - hasChangedAutomaticTypeDirectiveNames?(): boolean; + hasChangedAutomaticTypeDirectiveNames?: boolean; directoryExists?(directoryName: string): boolean; /* @@ -278,6 +278,7 @@ namespace ts { getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed; getProgram(): Program; From 5613be4907aab27f2669658570f17a1e64ecccce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Sep 2017 14:33:35 -0700 Subject: [PATCH 124/144] Only methods and constructors are bivariant in --strictFunctionTypes mode --- src/compiler/checker.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d3957652d8..ceb9d70847f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3549,7 +3549,7 @@ namespace ts { return; } - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length && isStrictSignature(resolved.callSignatures[0])) { + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); if (parenthesizeSignature) { writePunctuation(writer, SyntaxKind.OpenParenToken); @@ -3560,7 +3560,7 @@ namespace ts { } return; } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length && isStrictSignature(resolved.constructSignatures[0])) { + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { if (flags & TypeFormatFlags.InElementType) { writePunctuation(writer, SyntaxKind.OpenParenToken); } @@ -8522,17 +8522,6 @@ namespace ts { /*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False; } - // A signature is considered strict if it is declared in a function type literal, a constructor type - // literal, a function expression, an arrow function, or a function declaration with no overloads. A - // strict signature is subject to strict checking in strictFunctionTypes mode. - function isStrictSignature(signature: Signature) { - const declaration = signature.declaration; - const kind = declaration ? declaration.kind : SyntaxKind.Unknown; - return kind === SyntaxKind.FunctionType || kind === SyntaxKind.ConstructorType || - kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction || - (kind === SyntaxKind.FunctionDeclaration && getSingleCallSignature(getTypeOfSymbol(getSymbolOfNode(declaration)))); - } - type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; /** @@ -8558,7 +8547,9 @@ namespace ts { source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); } - const strictVariance = strictFunctionTypes && isStrictSignature(target); + const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; + const strictVariance = strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && + kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; let result = Ternary.True; const sourceThisType = getThisTypeOfSignature(source); From 1609196b22a4590039da899b57cabdc5ef7869ce Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Sep 2017 14:34:03 -0700 Subject: [PATCH 125/144] Accept new baselines --- .../reference/commentsClassMembers.js | 8 ++--- .../baselines/reference/commentsInterface.js | 8 ++--- tests/baselines/reference/jsDocTypeTag2.js | 30 ++++--------------- .../typeGuardFunctionOfFormThisErrors.js | 4 +-- 4 files changed, 10 insertions(+), 40 deletions(-) diff --git a/tests/baselines/reference/commentsClassMembers.js b/tests/baselines/reference/commentsClassMembers.js index d5cee0e1950..f53c64060a3 100644 --- a/tests/baselines/reference/commentsClassMembers.js +++ b/tests/baselines/reference/commentsClassMembers.js @@ -548,15 +548,11 @@ declare class c1 { } declare var i1: c1; declare var i1_p: number; -declare var i1_f: { - (b: number): number; -}; +declare var i1_f: (b: number) => number; declare var i1_r: number; declare var i1_prop: number; declare var i1_nc_p: number; -declare var i1_ncf: { - (b: number): number; -}; +declare var i1_ncf: (b: number) => number; declare var i1_ncr: number; declare var i1_ncprop: number; declare var i1_s_p: number; diff --git a/tests/baselines/reference/commentsInterface.js b/tests/baselines/reference/commentsInterface.js index b7ceffa58af..3deac9871f1 100644 --- a/tests/baselines/reference/commentsInterface.js +++ b/tests/baselines/reference/commentsInterface.js @@ -142,13 +142,9 @@ declare var i2_i_nc_x: number; declare var i2_i_nc_foo: (b: number) => string; declare var i2_i_nc_foo_r: string; declare var i2_i_r: number; -declare var i2_i_fnfoo: { - (b: number): string; -}; +declare var i2_i_fnfoo: (b: number) => string; declare var i2_i_fnfoo_r: string; -declare var i2_i_nc_fnfoo: { - (b: number): string; -}; +declare var i2_i_nc_fnfoo: (b: number) => string; declare var i2_i_nc_fnfoo_r: string; interface i3 { /** Comment i3 x*/ diff --git a/tests/baselines/reference/jsDocTypeTag2.js b/tests/baselines/reference/jsDocTypeTag2.js index bde3f3f9c1e..d54b4557f06 100644 --- a/tests/baselines/reference/jsDocTypeTag2.js +++ b/tests/baselines/reference/jsDocTypeTag2.js @@ -472,18 +472,6 @@ "text": " ", "kind": "space" }, - { - "text": "{", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": " ", - "kind": "space" - }, { "text": "(", "kind": "punctuation" @@ -509,7 +497,11 @@ "kind": "punctuation" }, { - "text": ":", + "text": " ", + "kind": "space" + }, + { + "text": "=>", "kind": "punctuation" }, { @@ -519,18 +511,6 @@ { "text": "number", "kind": "keyword" - }, - { - "text": ";", - "kind": "punctuation" - }, - { - "text": "\n", - "kind": "lineBreak" - }, - { - "text": "}", - "kind": "punctuation" } ], "documentation": [], diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js index 6ba90de54f1..84db5d4a4a9 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js @@ -154,6 +154,4 @@ declare let c: number | number[]; declare let holder: { invalidGuard: (c: any) => this is number; }; -declare let detached: { - (): this is FollowerGuard; -}; +declare let detached: () => this is FollowerGuard; From c626d9d47cc8e070a7d92ece1e39aacdaf80f425 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 28 Sep 2017 15:44:17 -0700 Subject: [PATCH 126/144] Accept new baselines --- .../strictFunctionTypesErrors.symbols | 366 +++++++++++++++ .../reference/strictFunctionTypesErrors.types | 418 ++++++++++++++++++ 2 files changed, 784 insertions(+) create mode 100644 tests/baselines/reference/strictFunctionTypesErrors.symbols create mode 100644 tests/baselines/reference/strictFunctionTypesErrors.types diff --git a/tests/baselines/reference/strictFunctionTypesErrors.symbols b/tests/baselines/reference/strictFunctionTypesErrors.symbols new file mode 100644 index 00000000000..ce3a81f524f --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypesErrors.symbols @@ -0,0 +1,366 @@ +=== tests/cases/compiler/strictFunctionTypesErrors.ts === +export {} + + +declare let f1: (x: Object) => Object; +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) +>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 3, 17)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let f2: (x: Object) => string; +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) +>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 4, 17)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let f3: (x: string) => Object; +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) +>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 5, 17)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let f4: (x: string) => string; +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) +>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 6, 17)) + +f1 = f2; // Ok +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) + +f1 = f3; // Error +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) + +f1 = f4; // Error +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) + +f2 = f1; // Error +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) + +f2 = f3; // Error +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) + +f2 = f4; // Error +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) + +f3 = f1; // Ok +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) + +f3 = f2; // Ok +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) + +f3 = f4; // Ok +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) + +f4 = f1; // Error +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) +>f1 : Symbol(f1, Decl(strictFunctionTypesErrors.ts, 3, 11)) + +f4 = f2; // Ok +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) +>f2 : Symbol(f2, Decl(strictFunctionTypesErrors.ts, 4, 11)) + +f4 = f3; // Error +>f4 : Symbol(f4, Decl(strictFunctionTypesErrors.ts, 6, 11)) +>f3 : Symbol(f3, Decl(strictFunctionTypesErrors.ts, 5, 11)) + +type Func = (x: T) => U; +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 24, 10)) +>U : Symbol(U, Decl(strictFunctionTypesErrors.ts, 24, 12)) +>x : Symbol(x, Decl(strictFunctionTypesErrors.ts, 24, 19)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 24, 10)) +>U : Symbol(U, Decl(strictFunctionTypesErrors.ts, 24, 12)) + +declare let g1: Func; +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let g2: Func; +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let g3: Func; +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let g4: Func; +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) + +g1 = g2; // Ok +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) + +g1 = g3; // Error +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) + +g1 = g4; // Error +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) + +g2 = g1; // Error +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) + +g2 = g3; // Error +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) + +g2 = g4; // Error +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) + +g3 = g1; // Ok +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) + +g3 = g2; // Ok +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) + +g3 = g4; // Ok +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) + +g4 = g1; // Error +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) +>g1 : Symbol(g1, Decl(strictFunctionTypesErrors.ts, 26, 11)) + +g4 = g2; // Ok +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) +>g2 : Symbol(g2, Decl(strictFunctionTypesErrors.ts, 27, 11)) + +g4 = g3; // Error +>g4 : Symbol(g4, Decl(strictFunctionTypesErrors.ts, 29, 11)) +>g3 : Symbol(g3, Decl(strictFunctionTypesErrors.ts, 28, 11)) + +declare let h1: Func, Object>; +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let h2: Func, string>; +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let h3: Func, Object>; +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let h4: Func, string>; +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) + +h1 = h2; // Ok +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) + +h1 = h3; // Ok +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) + +h1 = h4; // Ok +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) + +h2 = h1; // Error +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) + +h2 = h3; // Error +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) + +h2 = h4; // Ok +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) + +h3 = h1; // Error +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) + +h3 = h2; // Error +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) + +h3 = h4; // Ok +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) + +h4 = h1; // Error +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) +>h1 : Symbol(h1, Decl(strictFunctionTypesErrors.ts, 47, 11)) + +h4 = h2; // Error +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) +>h2 : Symbol(h2, Decl(strictFunctionTypesErrors.ts, 48, 11)) + +h4 = h3; // Error +>h4 : Symbol(h4, Decl(strictFunctionTypesErrors.ts, 50, 11)) +>h3 : Symbol(h3, Decl(strictFunctionTypesErrors.ts, 49, 11)) + +declare let i1: Func>; +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let i2: Func>; +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) + +declare let i3: Func>; +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + +declare let i4: Func>; +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) +>Func : Symbol(Func, Decl(strictFunctionTypesErrors.ts, 22, 8)) + +i1 = i2; // Error +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) + +i1 = i3; // Error +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) + +i1 = i4; // Error +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) + +i2 = i1; // Ok +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) + +i2 = i3; // Error +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) + +i2 = i4; // Error +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) + +i3 = i1; // Ok +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) + +i3 = i2; // Error +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) + +i3 = i4; // Error +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) + +i4 = i1; // Ok +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) +>i1 : Symbol(i1, Decl(strictFunctionTypesErrors.ts, 68, 11)) + +i4 = i2; // Ok +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) +>i2 : Symbol(i2, Decl(strictFunctionTypesErrors.ts, 69, 11)) + +i4 = i3; // Ok +>i4 : Symbol(i4, Decl(strictFunctionTypesErrors.ts, 71, 11)) +>i3 : Symbol(i3, Decl(strictFunctionTypesErrors.ts, 70, 11)) + +interface Animal { animal: void } +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) +>animal : Symbol(Animal.animal, Decl(strictFunctionTypesErrors.ts, 89, 18)) + +interface Dog extends Animal { dog: void } +>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33)) +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) +>dog : Symbol(Dog.dog, Decl(strictFunctionTypesErrors.ts, 90, 30)) + +interface Cat extends Animal { cat: void } +>Cat : Symbol(Cat, Decl(strictFunctionTypesErrors.ts, 90, 42)) +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) +>cat : Symbol(Cat.cat, Decl(strictFunctionTypesErrors.ts, 91, 30)) + +interface Comparer1 { +>Comparer1 : Symbol(Comparer1, Decl(strictFunctionTypesErrors.ts, 91, 42)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 93, 20)) + + compare(a: T, b: T): number; +>compare : Symbol(Comparer1.compare, Decl(strictFunctionTypesErrors.ts, 93, 24)) +>a : Symbol(a, Decl(strictFunctionTypesErrors.ts, 94, 12)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 93, 20)) +>b : Symbol(b, Decl(strictFunctionTypesErrors.ts, 94, 17)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 93, 20)) +} + +declare let animalComparer1: Comparer1; +>animalComparer1 : Symbol(animalComparer1, Decl(strictFunctionTypesErrors.ts, 97, 11)) +>Comparer1 : Symbol(Comparer1, Decl(strictFunctionTypesErrors.ts, 91, 42)) +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) + +declare let dogComparer1: Comparer1; +>dogComparer1 : Symbol(dogComparer1, Decl(strictFunctionTypesErrors.ts, 98, 11)) +>Comparer1 : Symbol(Comparer1, Decl(strictFunctionTypesErrors.ts, 91, 42)) +>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33)) + +animalComparer1 = dogComparer1; // Ok +>animalComparer1 : Symbol(animalComparer1, Decl(strictFunctionTypesErrors.ts, 97, 11)) +>dogComparer1 : Symbol(dogComparer1, Decl(strictFunctionTypesErrors.ts, 98, 11)) + +dogComparer1 = animalComparer1; // Ok +>dogComparer1 : Symbol(dogComparer1, Decl(strictFunctionTypesErrors.ts, 98, 11)) +>animalComparer1 : Symbol(animalComparer1, Decl(strictFunctionTypesErrors.ts, 97, 11)) + +interface Comparer2 { +>Comparer2 : Symbol(Comparer2, Decl(strictFunctionTypesErrors.ts, 101, 31)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 103, 20)) + + compare: (a: T, b: T) => number; +>compare : Symbol(Comparer2.compare, Decl(strictFunctionTypesErrors.ts, 103, 24)) +>a : Symbol(a, Decl(strictFunctionTypesErrors.ts, 104, 14)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 103, 20)) +>b : Symbol(b, Decl(strictFunctionTypesErrors.ts, 104, 19)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 103, 20)) +} + +declare let animalComparer2: Comparer2; +>animalComparer2 : Symbol(animalComparer2, Decl(strictFunctionTypesErrors.ts, 107, 11)) +>Comparer2 : Symbol(Comparer2, Decl(strictFunctionTypesErrors.ts, 101, 31)) +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) + +declare let dogComparer2: Comparer2; +>dogComparer2 : Symbol(dogComparer2, Decl(strictFunctionTypesErrors.ts, 108, 11)) +>Comparer2 : Symbol(Comparer2, Decl(strictFunctionTypesErrors.ts, 101, 31)) +>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33)) + +animalComparer2 = dogComparer2; // Error +>animalComparer2 : Symbol(animalComparer2, Decl(strictFunctionTypesErrors.ts, 107, 11)) +>dogComparer2 : Symbol(dogComparer2, Decl(strictFunctionTypesErrors.ts, 108, 11)) + +dogComparer2 = animalComparer2; // Ok +>dogComparer2 : Symbol(dogComparer2, Decl(strictFunctionTypesErrors.ts, 108, 11)) +>animalComparer2 : Symbol(animalComparer2, Decl(strictFunctionTypesErrors.ts, 107, 11)) + diff --git a/tests/baselines/reference/strictFunctionTypesErrors.types b/tests/baselines/reference/strictFunctionTypesErrors.types new file mode 100644 index 00000000000..f2d3b52a5b9 --- /dev/null +++ b/tests/baselines/reference/strictFunctionTypesErrors.types @@ -0,0 +1,418 @@ +=== tests/cases/compiler/strictFunctionTypesErrors.ts === +export {} + + +declare let f1: (x: Object) => Object; +>f1 : (x: Object) => Object +>x : Object +>Object : Object +>Object : Object + +declare let f2: (x: Object) => string; +>f2 : (x: Object) => string +>x : Object +>Object : Object + +declare let f3: (x: string) => Object; +>f3 : (x: string) => Object +>x : string +>Object : Object + +declare let f4: (x: string) => string; +>f4 : (x: string) => string +>x : string + +f1 = f2; // Ok +>f1 = f2 : (x: Object) => string +>f1 : (x: Object) => Object +>f2 : (x: Object) => string + +f1 = f3; // Error +>f1 = f3 : (x: string) => Object +>f1 : (x: Object) => Object +>f3 : (x: string) => Object + +f1 = f4; // Error +>f1 = f4 : (x: string) => string +>f1 : (x: Object) => Object +>f4 : (x: string) => string + +f2 = f1; // Error +>f2 = f1 : (x: Object) => Object +>f2 : (x: Object) => string +>f1 : (x: Object) => Object + +f2 = f3; // Error +>f2 = f3 : (x: string) => Object +>f2 : (x: Object) => string +>f3 : (x: string) => Object + +f2 = f4; // Error +>f2 = f4 : (x: string) => string +>f2 : (x: Object) => string +>f4 : (x: string) => string + +f3 = f1; // Ok +>f3 = f1 : (x: Object) => Object +>f3 : (x: string) => Object +>f1 : (x: Object) => Object + +f3 = f2; // Ok +>f3 = f2 : (x: Object) => string +>f3 : (x: string) => Object +>f2 : (x: Object) => string + +f3 = f4; // Ok +>f3 = f4 : (x: string) => string +>f3 : (x: string) => Object +>f4 : (x: string) => string + +f4 = f1; // Error +>f4 = f1 : (x: Object) => Object +>f4 : (x: string) => string +>f1 : (x: Object) => Object + +f4 = f2; // Ok +>f4 = f2 : (x: Object) => string +>f4 : (x: string) => string +>f2 : (x: Object) => string + +f4 = f3; // Error +>f4 = f3 : (x: string) => Object +>f4 : (x: string) => string +>f3 : (x: string) => Object + +type Func = (x: T) => U; +>Func : (x: T) => U +>T : T +>U : U +>x : T +>T : T +>U : U + +declare let g1: Func; +>g1 : (x: Object) => Object +>Func : (x: T) => U +>Object : Object +>Object : Object + +declare let g2: Func; +>g2 : (x: Object) => string +>Func : (x: T) => U +>Object : Object + +declare let g3: Func; +>g3 : (x: string) => Object +>Func : (x: T) => U +>Object : Object + +declare let g4: Func; +>g4 : (x: string) => string +>Func : (x: T) => U + +g1 = g2; // Ok +>g1 = g2 : (x: Object) => string +>g1 : (x: Object) => Object +>g2 : (x: Object) => string + +g1 = g3; // Error +>g1 = g3 : (x: string) => Object +>g1 : (x: Object) => Object +>g3 : (x: string) => Object + +g1 = g4; // Error +>g1 = g4 : (x: string) => string +>g1 : (x: Object) => Object +>g4 : (x: string) => string + +g2 = g1; // Error +>g2 = g1 : (x: Object) => Object +>g2 : (x: Object) => string +>g1 : (x: Object) => Object + +g2 = g3; // Error +>g2 = g3 : (x: string) => Object +>g2 : (x: Object) => string +>g3 : (x: string) => Object + +g2 = g4; // Error +>g2 = g4 : (x: string) => string +>g2 : (x: Object) => string +>g4 : (x: string) => string + +g3 = g1; // Ok +>g3 = g1 : (x: Object) => Object +>g3 : (x: string) => Object +>g1 : (x: Object) => Object + +g3 = g2; // Ok +>g3 = g2 : (x: Object) => string +>g3 : (x: string) => Object +>g2 : (x: Object) => string + +g3 = g4; // Ok +>g3 = g4 : (x: string) => string +>g3 : (x: string) => Object +>g4 : (x: string) => string + +g4 = g1; // Error +>g4 = g1 : (x: Object) => Object +>g4 : (x: string) => string +>g1 : (x: Object) => Object + +g4 = g2; // Ok +>g4 = g2 : (x: Object) => string +>g4 : (x: string) => string +>g2 : (x: Object) => string + +g4 = g3; // Error +>g4 = g3 : (x: string) => Object +>g4 : (x: string) => string +>g3 : (x: string) => Object + +declare let h1: Func, Object>; +>h1 : (x: Func) => Object +>Func : (x: T) => U +>Func : (x: T) => U +>Object : Object +>Object : Object + +declare let h2: Func, string>; +>h2 : (x: Func) => string +>Func : (x: T) => U +>Func : (x: T) => U +>Object : Object + +declare let h3: Func, Object>; +>h3 : (x: Func) => Object +>Func : (x: T) => U +>Func : (x: T) => U +>Object : Object + +declare let h4: Func, string>; +>h4 : (x: Func) => string +>Func : (x: T) => U +>Func : (x: T) => U + +h1 = h2; // Ok +>h1 = h2 : (x: Func) => string +>h1 : (x: Func) => Object +>h2 : (x: Func) => string + +h1 = h3; // Ok +>h1 = h3 : (x: Func) => Object +>h1 : (x: Func) => Object +>h3 : (x: Func) => Object + +h1 = h4; // Ok +>h1 = h4 : (x: Func) => string +>h1 : (x: Func) => Object +>h4 : (x: Func) => string + +h2 = h1; // Error +>h2 = h1 : (x: Func) => Object +>h2 : (x: Func) => string +>h1 : (x: Func) => Object + +h2 = h3; // Error +>h2 = h3 : (x: Func) => Object +>h2 : (x: Func) => string +>h3 : (x: Func) => Object + +h2 = h4; // Ok +>h2 = h4 : (x: Func) => string +>h2 : (x: Func) => string +>h4 : (x: Func) => string + +h3 = h1; // Error +>h3 = h1 : (x: Func) => Object +>h3 : (x: Func) => Object +>h1 : (x: Func) => Object + +h3 = h2; // Error +>h3 = h2 : (x: Func) => string +>h3 : (x: Func) => Object +>h2 : (x: Func) => string + +h3 = h4; // Ok +>h3 = h4 : (x: Func) => string +>h3 : (x: Func) => Object +>h4 : (x: Func) => string + +h4 = h1; // Error +>h4 = h1 : (x: Func) => Object +>h4 : (x: Func) => string +>h1 : (x: Func) => Object + +h4 = h2; // Error +>h4 = h2 : (x: Func) => string +>h4 : (x: Func) => string +>h2 : (x: Func) => string + +h4 = h3; // Error +>h4 = h3 : (x: Func) => Object +>h4 : (x: Func) => string +>h3 : (x: Func) => Object + +declare let i1: Func>; +>i1 : (x: Object) => Func +>Func : (x: T) => U +>Object : Object +>Func : (x: T) => U +>Object : Object + +declare let i2: Func>; +>i2 : (x: Object) => Func +>Func : (x: T) => U +>Object : Object +>Func : (x: T) => U + +declare let i3: Func>; +>i3 : (x: string) => Func +>Func : (x: T) => U +>Func : (x: T) => U +>Object : Object + +declare let i4: Func>; +>i4 : (x: string) => Func +>Func : (x: T) => U +>Func : (x: T) => U + +i1 = i2; // Error +>i1 = i2 : (x: Object) => Func +>i1 : (x: Object) => Func +>i2 : (x: Object) => Func + +i1 = i3; // Error +>i1 = i3 : (x: string) => Func +>i1 : (x: Object) => Func +>i3 : (x: string) => Func + +i1 = i4; // Error +>i1 = i4 : (x: string) => Func +>i1 : (x: Object) => Func +>i4 : (x: string) => Func + +i2 = i1; // Ok +>i2 = i1 : (x: Object) => Func +>i2 : (x: Object) => Func +>i1 : (x: Object) => Func + +i2 = i3; // Error +>i2 = i3 : (x: string) => Func +>i2 : (x: Object) => Func +>i3 : (x: string) => Func + +i2 = i4; // Error +>i2 = i4 : (x: string) => Func +>i2 : (x: Object) => Func +>i4 : (x: string) => Func + +i3 = i1; // Ok +>i3 = i1 : (x: Object) => Func +>i3 : (x: string) => Func +>i1 : (x: Object) => Func + +i3 = i2; // Error +>i3 = i2 : (x: Object) => Func +>i3 : (x: string) => Func +>i2 : (x: Object) => Func + +i3 = i4; // Error +>i3 = i4 : (x: string) => Func +>i3 : (x: string) => Func +>i4 : (x: string) => Func + +i4 = i1; // Ok +>i4 = i1 : (x: Object) => Func +>i4 : (x: string) => Func +>i1 : (x: Object) => Func + +i4 = i2; // Ok +>i4 = i2 : (x: Object) => Func +>i4 : (x: string) => Func +>i2 : (x: Object) => Func + +i4 = i3; // Ok +>i4 = i3 : (x: string) => Func +>i4 : (x: string) => Func +>i3 : (x: string) => Func + +interface Animal { animal: void } +>Animal : Animal +>animal : void + +interface Dog extends Animal { dog: void } +>Dog : Dog +>Animal : Animal +>dog : void + +interface Cat extends Animal { cat: void } +>Cat : Cat +>Animal : Animal +>cat : void + +interface Comparer1 { +>Comparer1 : Comparer1 +>T : T + + compare(a: T, b: T): number; +>compare : (a: T, b: T) => number +>a : T +>T : T +>b : T +>T : T +} + +declare let animalComparer1: Comparer1; +>animalComparer1 : Comparer1 +>Comparer1 : Comparer1 +>Animal : Animal + +declare let dogComparer1: Comparer1; +>dogComparer1 : Comparer1 +>Comparer1 : Comparer1 +>Dog : Dog + +animalComparer1 = dogComparer1; // Ok +>animalComparer1 = dogComparer1 : Comparer1 +>animalComparer1 : Comparer1 +>dogComparer1 : Comparer1 + +dogComparer1 = animalComparer1; // Ok +>dogComparer1 = animalComparer1 : Comparer1 +>dogComparer1 : Comparer1 +>animalComparer1 : Comparer1 + +interface Comparer2 { +>Comparer2 : Comparer2 +>T : T + + compare: (a: T, b: T) => number; +>compare : (a: T, b: T) => number +>a : T +>T : T +>b : T +>T : T +} + +declare let animalComparer2: Comparer2; +>animalComparer2 : Comparer2 +>Comparer2 : Comparer2 +>Animal : Animal + +declare let dogComparer2: Comparer2; +>dogComparer2 : Comparer2 +>Comparer2 : Comparer2 +>Dog : Dog + +animalComparer2 = dogComparer2; // Error +>animalComparer2 = dogComparer2 : Comparer2 +>animalComparer2 : Comparer2 +>dogComparer2 : Comparer2 + +dogComparer2 = animalComparer2; // Ok +>dogComparer2 = animalComparer2 : Comparer2 +>dogComparer2 : Comparer2 +>animalComparer2 : Comparer2 + From 9097a07758d429995e372d8d0194fa8c6ed5c2a0 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 28 Sep 2017 15:25:06 -0700 Subject: [PATCH 127/144] Improve insertion positions of extracted constants ...mostly by putting them closer to the extraction site. They now also follow all leading comments in a file (unfortunately, including the doc comment on the first declaration). Bonus: Special case declaration lists - declare the new variable as part of the list, rather than before it, in case it depends on an earlier entry. --- src/harness/unittests/extractConstants.ts | 141 +++++++++++++++++- src/harness/unittests/extractFunctions.ts | 2 +- src/harness/unittests/extractTestHelpers.ts | 2 +- src/services/refactors/extractSymbol.ts | 136 +++++++++++++---- ...ractConstant_BlockScopes_NoDependencies.js | 4 +- ...ractConstant_BlockScopes_NoDependencies.ts | 4 +- ...xtractConstant_ClassInsertionPosition1.js} | 0 ...xtractConstant_ClassInsertionPosition1.ts} | 0 ...extractConstant_ClassInsertionPosition2.js | 34 +++++ ...extractConstant_ClassInsertionPosition2.ts | 46 ++++++ ...extractConstant_ClassInsertionPosition3.js | 34 +++++ ...extractConstant_ClassInsertionPosition3.ts | 46 ++++++ .../extractConstant_Directive.js | 14 ++ .../extractConstant_Directive.ts | 14 ++ .../extractConstant_MultipleHeaders.js | 22 +++ .../extractConstant_MultipleHeaders.ts | 22 +++ .../extractConstant_PinnedComment.js | 14 ++ .../extractConstant_PinnedComment.ts | 14 ++ ...ractConstant_PinnedCommentAndDocComment.js | 16 ++ ...ractConstant_PinnedCommentAndDocComment.ts | 16 ++ ...actConstant_StatementInsertionPosition1.js | 16 ++ ...actConstant_StatementInsertionPosition1.ts | 16 ++ ...actConstant_StatementInsertionPosition2.js | 31 ++++ ...actConstant_StatementInsertionPosition2.ts | 31 ++++ ...actConstant_StatementInsertionPosition3.js | 14 ++ ...actConstant_StatementInsertionPosition3.ts | 14 ++ ...actConstant_StatementInsertionPosition4.js | 28 ++++ ...actConstant_StatementInsertionPosition4.ts | 28 ++++ ...actConstant_StatementInsertionPosition5.js | 42 ++++++ ...actConstant_StatementInsertionPosition5.ts | 42 ++++++ ...actConstant_StatementInsertionPosition6.js | 14 ++ ...actConstant_StatementInsertionPosition6.ts | 22 +++ ...actConstant_StatementInsertionPosition7.js | 37 +++++ ...actConstant_StatementInsertionPosition7.ts | 50 +++++++ .../extractConstant_TripleSlash.js | 14 ++ .../extractConstant_TripleSlash.ts | 14 ++ ...ractConstant_VariableList_MultipleLines.js | 6 + ...ractConstant_VariableList_MultipleLines.ts | 6 + .../extractConstant_VariableList_const.js | 4 + .../extractConstant_VariableList_const.ts | 4 + .../extractConstant_VariableList_let.js | 4 + .../extractConstant_VariableList_let.ts | 4 + 42 files changed, 983 insertions(+), 39 deletions(-) rename tests/baselines/reference/extractConstant/{extractConstant_ClassInsertionPosition.js => extractConstant_ClassInsertionPosition1.js} (100%) rename tests/baselines/reference/extractConstant/{extractConstant_ClassInsertionPosition.ts => extractConstant_ClassInsertionPosition1.ts} (100%) create mode 100644 tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_Directive.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_Directive.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index e2c64afb526..d37f9b2036c 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -40,7 +40,7 @@ namespace ts { } }`); - testExtractConstant("extractConstant_ClassInsertionPosition", + testExtractConstant("extractConstant_ClassInsertionPosition1", `class C { a = 1; b = 2; @@ -51,6 +51,28 @@ namespace ts { } }`); + testExtractConstant("extractConstant_ClassInsertionPosition2", + `class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + let x = [#|1|]; + } +}`); + + testExtractConstant("extractConstant_ClassInsertionPosition3", + `class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + let x = [#|1|]; + } +}`); + testExtractConstant("extractConstant_Parameters", `function F() { let w = 1; @@ -62,7 +84,7 @@ namespace ts { let x = [#|t + 1|]; }`); -// TODO (acasey): handle repeated substitution +// TODO (18857): handle repeated substitution // testExtractConstant("extractConstant_RepeatedSubstitution", // `namespace X { // export const j = 10; @@ -75,6 +97,121 @@ namespace ts { let x = [#|i + 1|]; } }`); + + testExtractConstant("extractConstant_VariableList_const", + `const a = 1, b = [#|a + 1|];`); + + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_let", + `let a = 1, b = [#|a + 1|];`); + + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_VariableList_MultipleLines", + `const /*About A*/a = 1, + /*About B*/b = [#|a + 1|];`); + + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + // `i` doesn't bind in the target scope (file-level), so the extraction is disallowed. + // TODO (17098): should probably allow extraction into the same scope + testExtractConstantFailed("extractConstant_BlockScopeMismatch", ` +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const x = [#|i + 1|]; + } +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition1", ` +const i = 0; +for (let j = 0; j < 10; j++) { + const x = [#|i + 1|]; +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition2", ` +const i = 0; +function F() { + for (let j = 0; j < 10; j++) { + const x = [#|i + 1|]; + } +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition3", ` +for (let j = 0; j < 10; j++) { + const x = [#|2 + 1|]; +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition4", ` +function F() { + for (let j = 0; j < 10; j++) { + const x = [#|2 + 1|]; + } +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition5", ` +function F0() { + function F1() { + function F2(x = [#|2 + 1|]) { + } + } +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition6", ` +class C { + x = [#|2 + 1|]; +} + `); + + testExtractConstant("extractConstant_StatementInsertionPosition7", ` +const i = 0; +class C { + M() { + for (let j = 0; j < 10; j++) { + x = [#|i + 1|]; + } + } +} + `); + + testExtractConstant("extractConstant_TripleSlash", ` +/// + +const x = [#|2 + 1|]; + `); + + testExtractConstant("extractConstant_PinnedComment", ` +/*! Copyright */ + +const x = [#|2 + 1|]; + `); + + testExtractConstant("extractConstant_Directive", ` +"strict"; + +const x = [#|2 + 1|]; + `); + + testExtractConstant("extractConstant_MultipleHeaders", ` +/*! Copyright */ + +/// + +"strict"; + +const x = [#|2 + 1|]; + `); + + // NOTE: this test isn't normative - it just documents our sub-optimal behavior. + testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` +/*! Copyright */ + +/* About x */ +const x = [#|2 + 1|]; + `); }); function testExtractConstant(caption: string, text: string) { diff --git a/src/harness/unittests/extractFunctions.ts b/src/harness/unittests/extractFunctions.ts index 522ea7b1293..c4cbadacdc3 100644 --- a/src/harness/unittests/extractFunctions.ts +++ b/src/harness/unittests/extractFunctions.ts @@ -365,7 +365,7 @@ function parsePrimaryExpression(): any { [#|function G() { }|] }`); -// TODO (acasey): handle repeated substitution +// TODO (18857): handle repeated substitution // testExtractFunction("extractFunction_RepeatedSubstitution", // `namespace X { // export const j = 10; diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts index ea1cccd32ea..0d739919e1f 100644 --- a/src/harness/unittests/extractTestHelpers.ts +++ b/src/harness/unittests/extractTestHelpers.ts @@ -113,7 +113,7 @@ namespace ts { if (hasSyntacticDiagnostics(program)) { // Don't bother generating JS baselines for inputs that aren't valid JS. - assert.equal(Extension.Js, extension); + assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file"); return; } diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 375acb7e585..ad4da2c61fc 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -890,33 +890,56 @@ namespace ts.refactor.extractSymbol { createIdentifier(localNameText)); // Declare - const minInsertionPos = node.end; - const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope); + const maxInsertionPos = node.pos; + const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter }); // Consume changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter }); } else { - const newVariable = createVariableStatement( - /*modifiers*/ undefined, - createVariableDeclarationList( - [createVariableDeclaration(localNameText, variableType, initializer)], - NodeFlags.Const)); + const newVariableDeclaration = createVariableDeclaration(localNameText, variableType, initializer); - // If the parent is an expression statement, replace the statement with the declaration - if (node.parent.kind === SyntaxKind.ExpressionStatement) { - changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariable], { nodeSeparator: context.newLineCharacter }); - } - else { + // If the node is part of an initializer in a list of variable declarations, insert a new + // variable declaration into the list (in case it depends on earlier ones). + // CONSIDER: If the declaration list isn't const, we might want to split it into multiple + // lists so that the newly extracted one can be const. + const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope); + if (oldVariableDeclaration) { // Declare - const minInsertionPos = node.end; - const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope); - changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter }); + // CONSIDER: could detect that each is on a separate line + changeTracker.insertNodeAt(context.file, oldVariableDeclaration.getStart(), newVariableDeclaration, { suffix: ", " }); // Consume const localReference = createIdentifier(localNameText); - changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter }); + changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference); + } + else if (node.parent.kind === SyntaxKind.ExpressionStatement) { + // If the parent is an expression statement, replace the statement with the declaration. + const newVariableStatement = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariableStatement], { nodeSeparator: context.newLineCharacter }); + } + else { + const newVariableStatement = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const)); + + // Declare + const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); + if (nodeToInsertBefore.pos === 0) { + // If we're at the beginning of the file, we need to take care not to insert before header comments + // (e.g. copyright, triple-slash references). + changeTracker.insertNodeAt(context.file, nodeToInsertBefore.getStart(), newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter }); + } + else { + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter }); + } + + // Consume + const localReference = createIdentifier(localNameText); + changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference); } } @@ -927,6 +950,22 @@ namespace ts.refactor.extractSymbol { return { renameFilename, renameLocation, edits }; } + function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { + let prevNode = undefined; + while (node !== undefined && node !== scope) { + if (isVariableDeclaration(node) && + node.initializer === prevNode && + isVariableDeclarationList(node.parent) && + node.parent.declarations.length > 1) { + + return node; + } + + prevNode = node; + node = node.parent; + } + } + /** * @return The index of the (only) reference to the extracted symbol. We want the cursor * to be on the reference, rather than the declaration, because it's closer to where the @@ -1109,26 +1148,61 @@ namespace ts.refactor.extractSymbol { child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); } - // TODO (acasey): need to dig into nested statements - // TODO (acasey): don't insert before pinned comments, directives, or triple-slash references - function getNodeToInsertConstantBefore(maxPos: number, scope: Scope): Node { - const children = getStatementsOrClassElements(scope); - Debug.assert(children.length > 0); // There must be at least one child, since we extracted from one. + function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): Node { + const members = scope.members; + Debug.assert(members.length > 0); // There must be at least one child, since we extracted from one. - const isClassLikeScope = isClassLike(scope); - let prevChild: Statement | ClassElement | undefined = undefined; - for (const child of children) { - if (child.pos >= maxPos) { - break; + let prevMember: ClassElement | undefined = undefined; + let allProperties = true; + for (const member of members) { + if (member.pos > maxPos) { + return prevMember || members[0]; } - prevChild = child; - if (isClassLikeScope && !isPropertyDeclaration(child)) { - break; + if (allProperties && !isPropertyDeclaration(member)) { + // If it is non-vacuously true that all preceding members are properties, + // insert before the current member (i.e. at the end of the list of properties). + if (prevMember !== undefined) { + return member; + } + + allProperties = false; + } + prevMember = member; + } + + Debug.assert(prevMember !== undefined); // If the loop didn't return, then it did set prevMember. + return prevMember; + } + + function getNodeToInsertConstantBefore(node: Node, scope: Scope): Node { + Debug.assert(!isClassLike(scope)); + + let prevScope: Scope | undefined = undefined; + for (let curr = node; curr !== scope; curr = curr.parent) { + if (isScope(curr)) { + prevScope = curr; } } - Debug.assert(prevChild !== undefined); - return prevChild; + for (let curr = (prevScope || node).parent; ; curr = curr.parent) { + if (isBlockLike(curr)) { + let prevStatement = undefined; + for (const statement of curr.statements) { + if (statement.pos > node.pos) { + break; + } + prevStatement = statement; + } + // There must be at least one statement since we started in one. + Debug.assert(prevStatement !== undefined); + return prevStatement; + } + + if (curr === scope) { + Debug.fail("Didn't encounter a block-like before encountering scope"); + break; + } + } } function getPropertyAssignmentsForWrites(writes: ReadonlyArray): ShorthandPropertyAssignment[] { diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js index 25609a3a801..b934af59550 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js @@ -5,10 +5,10 @@ for (let i = 0; i < 10; i++) { } } // ==SCOPE::Extract to constant in global scope== -const newLocal = 1; - for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { + const newLocal = 1; + let x = /*RENAME*/newLocal; } } \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts index 25609a3a801..b934af59550 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts @@ -5,10 +5,10 @@ for (let i = 0; i < 10; i++) { } } // ==SCOPE::Extract to constant in global scope== -const newLocal = 1; - for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { + const newLocal = 1; + let x = /*RENAME*/newLocal; } } \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js similarity index 100% rename from tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.js rename to tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts similarity index 100% rename from tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts rename to tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js new file mode 100644 index 00000000000..137fc5ff0bb --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js @@ -0,0 +1,34 @@ +// ==ORIGINAL== +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M3== +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts new file mode 100644 index 00000000000..5bd239c0e63 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts @@ -0,0 +1,46 @@ +// ==ORIGINAL== +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M3== +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to readonly field in class 'C'== +class C { + a = 1; + private readonly newProperty = 1; + + M1() { } + b = 2; + M2() { } + M3() { + let x = this./*RENAME*/newProperty; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + a = 1; + M1() { } + b = 2; + M2() { } + M3() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js new file mode 100644 index 00000000000..1679e46d8d5 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js @@ -0,0 +1,34 @@ +// ==ORIGINAL== +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M3== +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts new file mode 100644 index 00000000000..4cc44565fa0 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts @@ -0,0 +1,46 @@ +// ==ORIGINAL== +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M3== +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to readonly field in class 'C'== +class C { + M1() { } + a = 1; + b = 2; + M2() { } + private readonly newProperty = 1; + + M3() { + let x = this./*RENAME*/newProperty; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + M1() { } + a = 1; + b = 2; + M2() { } + M3() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.js b/tests/baselines/reference/extractConstant/extractConstant_Directive.js new file mode 100644 index 00000000000..5ba48e27de9 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +"strict"; + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +"strict"; + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.ts b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts new file mode 100644 index 00000000000..5ba48e27de9 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +"strict"; + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +"strict"; + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js new file mode 100644 index 00000000000..d2d0f14c01e --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js @@ -0,0 +1,22 @@ +// ==ORIGINAL== + +/*! Copyright */ + +/// + +"strict"; + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +/// + +"strict"; + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts new file mode 100644 index 00000000000..d2d0f14c01e --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts @@ -0,0 +1,22 @@ +// ==ORIGINAL== + +/*! Copyright */ + +/// + +"strict"; + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +/// + +"strict"; + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js new file mode 100644 index 00000000000..a372457fc48 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +/*! Copyright */ + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts new file mode 100644 index 00000000000..a372457fc48 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +/*! Copyright */ + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js new file mode 100644 index 00000000000..763473d49cd --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +/*! Copyright */ + +/* About x */ +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +/* About x */ +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts new file mode 100644 index 00000000000..763473d49cd --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +/*! Copyright */ + +/* About x */ +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/*! Copyright */ + +/* About x */ +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js new file mode 100644 index 00000000000..f881a021232 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +const i = 0; +for (let j = 0; j < 10; j++) { + const x = i + 1; +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts new file mode 100644 index 00000000000..f881a021232 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== + +const i = 0; +for (let j = 0; j < 10; j++) { + const x = i + 1; +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js new file mode 100644 index 00000000000..7ffd89161a2 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js @@ -0,0 +1,31 @@ +// ==ORIGINAL== + +const i = 0; +function F() { + for (let j = 0; j < 10; j++) { + const x = i + 1; + } +} + +// ==SCOPE::Extract to constant in function 'F'== + +const i = 0; +function F() { + for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; + } +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +const newLocal = i + 1; + +function F() { + for (let j = 0; j < 10; j++) { + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts new file mode 100644 index 00000000000..7ffd89161a2 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts @@ -0,0 +1,31 @@ +// ==ORIGINAL== + +const i = 0; +function F() { + for (let j = 0; j < 10; j++) { + const x = i + 1; + } +} + +// ==SCOPE::Extract to constant in function 'F'== + +const i = 0; +function F() { + for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; + } +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +const newLocal = i + 1; + +function F() { + for (let j = 0; j < 10; j++) { + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js new file mode 100644 index 00000000000..6ba0b2dfaae --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +for (let j = 0; j < 10; j++) { + const x = 2 + 1; +} + +// ==SCOPE::Extract to constant in global scope== + +for (let j = 0; j < 10; j++) { + const newLocal = 2 + 1; + + const x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts new file mode 100644 index 00000000000..6ba0b2dfaae --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +for (let j = 0; j < 10; j++) { + const x = 2 + 1; +} + +// ==SCOPE::Extract to constant in global scope== + +for (let j = 0; j < 10; j++) { + const newLocal = 2 + 1; + + const x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js new file mode 100644 index 00000000000..19fd25382e8 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js @@ -0,0 +1,28 @@ +// ==ORIGINAL== + +function F() { + for (let j = 0; j < 10; j++) { + const x = 2 + 1; + } +} + +// ==SCOPE::Extract to constant in function 'F'== + +function F() { + for (let j = 0; j < 10; j++) { + const newLocal = 2 + 1; + + const x = /*RENAME*/newLocal; + } +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +function F() { + for (let j = 0; j < 10; j++) { + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts new file mode 100644 index 00000000000..19fd25382e8 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts @@ -0,0 +1,28 @@ +// ==ORIGINAL== + +function F() { + for (let j = 0; j < 10; j++) { + const x = 2 + 1; + } +} + +// ==SCOPE::Extract to constant in function 'F'== + +function F() { + for (let j = 0; j < 10; j++) { + const newLocal = 2 + 1; + + const x = /*RENAME*/newLocal; + } +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +function F() { + for (let j = 0; j < 10; j++) { + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js new file mode 100644 index 00000000000..a19bd6afa28 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js @@ -0,0 +1,42 @@ +// ==ORIGINAL== + +function F0() { + function F1() { + function F2(x = 2 + 1) { + } + } +} + +// ==SCOPE::Extract to constant in function 'F1'== + +function F0() { + function F1() { + const newLocal = 2 + 1; + + function F2(x = /*RENAME*/newLocal) { + } + } +} + +// ==SCOPE::Extract to constant in function 'F0'== + +function F0() { + const newLocal = 2 + 1; + + function F1() { + function F2(x = /*RENAME*/newLocal) { + } + } +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +function F0() { + function F1() { + function F2(x = /*RENAME*/newLocal) { + } + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts new file mode 100644 index 00000000000..a19bd6afa28 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts @@ -0,0 +1,42 @@ +// ==ORIGINAL== + +function F0() { + function F1() { + function F2(x = 2 + 1) { + } + } +} + +// ==SCOPE::Extract to constant in function 'F1'== + +function F0() { + function F1() { + const newLocal = 2 + 1; + + function F2(x = /*RENAME*/newLocal) { + } + } +} + +// ==SCOPE::Extract to constant in function 'F0'== + +function F0() { + const newLocal = 2 + 1; + + function F1() { + function F2(x = /*RENAME*/newLocal) { + } + } +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +function F0() { + function F1() { + function F2(x = /*RENAME*/newLocal) { + } + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js new file mode 100644 index 00000000000..06a665140d9 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +class C { + x = 2 + 1; +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +class C { + x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts new file mode 100644 index 00000000000..a34b0a9ecc3 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts @@ -0,0 +1,22 @@ +// ==ORIGINAL== + +class C { + x = 2 + 1; +} + +// ==SCOPE::Extract to readonly field in class 'C'== + +class C { + private readonly newProperty = 2 + 1; + + x = this./*RENAME*/newProperty; +} + +// ==SCOPE::Extract to constant in global scope== + +const newLocal = 2 + 1; + +class C { + x = /*RENAME*/newLocal; +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js new file mode 100644 index 00000000000..f73cba1b34c --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js @@ -0,0 +1,37 @@ +// ==ORIGINAL== + +const i = 0; +class C { + M() { + for (let j = 0; j < 10; j++) { + x = i + 1; + } + } +} + +// ==SCOPE::Extract to constant in method 'M== + +const i = 0; +class C { + M() { + for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + x = /*RENAME*/newLocal; + } + } +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +const newLocal = i + 1; + +class C { + M() { + for (let j = 0; j < 10; j++) { + x = /*RENAME*/newLocal; + } + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts new file mode 100644 index 00000000000..308df5785fb --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts @@ -0,0 +1,50 @@ +// ==ORIGINAL== + +const i = 0; +class C { + M() { + for (let j = 0; j < 10; j++) { + x = i + 1; + } + } +} + +// ==SCOPE::Extract to constant in method 'M== + +const i = 0; +class C { + M() { + for (let j = 0; j < 10; j++) { + const newLocal: any = i + 1; + + x = /*RENAME*/newLocal; + } + } +} + +// ==SCOPE::Extract to readonly field in class 'C'== + +const i = 0; +class C { + private readonly newProperty: any = i + 1; + + M() { + for (let j = 0; j < 10; j++) { + x = this./*RENAME*/newProperty; + } + } +} + +// ==SCOPE::Extract to constant in global scope== + +const i = 0; +const newLocal: any = i + 1; + +class C { + M() { + for (let j = 0; j < 10; j++) { + x = /*RENAME*/newLocal; + } + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js new file mode 100644 index 00000000000..5003e8aa33e --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +/// + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/// + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts new file mode 100644 index 00000000000..5003e8aa33e --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +/// + +const x = 2 + 1; + +// ==SCOPE::Extract to constant in global scope== + +/// + +const newLocal = 2 + 1; + +const x = /*RENAME*/newLocal; + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js new file mode 100644 index 00000000000..7582195a458 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js @@ -0,0 +1,6 @@ +// ==ORIGINAL== +const /*About A*/a = 1, + /*About B*/b = a + 1; +// ==SCOPE::Extract to constant in global scope== +const /*About A*/a = 1, + /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts new file mode 100644 index 00000000000..7582195a458 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts @@ -0,0 +1,6 @@ +// ==ORIGINAL== +const /*About A*/a = 1, + /*About B*/b = a + 1; +// ==SCOPE::Extract to constant in global scope== +const /*About A*/a = 1, + /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js new file mode 100644 index 00000000000..efcc7a5a00c --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js @@ -0,0 +1,4 @@ +// ==ORIGINAL== +const a = 1, b = a + 1; +// ==SCOPE::Extract to constant in global scope== +const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts new file mode 100644 index 00000000000..efcc7a5a00c --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts @@ -0,0 +1,4 @@ +// ==ORIGINAL== +const a = 1, b = a + 1; +// ==SCOPE::Extract to constant in global scope== +const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js new file mode 100644 index 00000000000..dc0796596cb --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js @@ -0,0 +1,4 @@ +// ==ORIGINAL== +let a = 1, b = a + 1; +// ==SCOPE::Extract to constant in global scope== +let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts new file mode 100644 index 00000000000..dc0796596cb --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts @@ -0,0 +1,4 @@ +// ==ORIGINAL== +let a = 1, b = a + 1; +// ==SCOPE::Extract to constant in global scope== +let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file From 936f98d9adca8530da7af3f7ccbb4ce1b27b6a93 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 29 Sep 2017 16:01:14 -0700 Subject: [PATCH 128/144] Addressing CR feedback --- src/compiler/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c34f0ad5499..01036862980 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3344,6 +3344,7 @@ namespace ts { typeArguments?: Type[]; // Type reference type arguments (undefined if none) } + /* @internal */ export const enum Variance { Invariant = 0, // Neither covariant nor contravariant Covariant = 1, // Covariant @@ -3356,6 +3357,7 @@ namespace ts { export interface GenericType extends InterfaceType, TypeReference { /* @internal */ instantiations: Map; // Generic instantiation cache + /* @internal */ variances?: Variance[]; // Variance of each type parameter } From c08308a0f13bb4e3e0462865bb7d17611540477c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 29 Sep 2017 17:37:39 -0700 Subject: [PATCH 129/144] Reuse getSourceFileImportLocation --- src/services/codefixes/importFixes.ts | 22 ------------------- src/services/refactors/extractSymbol.ts | 9 ++++++-- src/services/utilities.ts | 22 +++++++++++++++++++ ...ractConstant_PinnedCommentAndDocComment.js | 2 +- ...ractConstant_PinnedCommentAndDocComment.ts | 2 +- ...actConstant_StatementInsertionPosition4.js | 1 - ...actConstant_StatementInsertionPosition4.ts | 1 - ...actConstant_StatementInsertionPosition5.js | 1 - ...actConstant_StatementInsertionPosition5.ts | 1 - ...actConstant_StatementInsertionPosition6.js | 1 - ...actConstant_StatementInsertionPosition6.ts | 1 - 11 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 96f4b7ad4b5..f516bc15de3 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -407,28 +407,6 @@ namespace ts.codefix { moduleSpecifierWithoutQuotes ); - function getSourceFileImportLocation(node: SourceFile) { - // For a source file, it is possible there are detached comments we should not skip - const text = node.text; - let ranges = getLeadingCommentRanges(text, 0); - if (!ranges) return 0; - let position = 0; - // However we should still skip a pinned comment at the top - if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) { - position = ranges[0].end + 1; - ranges = ranges.slice(1); - } - // As well as any triple slash references - for (const range of ranges) { - if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(node.text, range.pos, range.end)) { - position = range.end + 1; - continue; - } - break; - } - return position; - } - function getSingleQuoteStyleFromExistingImports() { const firstModuleSpecifier = forEach(sourceFile.statements, node => { if (isImportDeclaration(node) || isExportDeclaration(node)) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index ad4da2c61fc..549bf653e9d 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -930,8 +930,13 @@ namespace ts.refactor.extractSymbol { const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope); if (nodeToInsertBefore.pos === 0) { // If we're at the beginning of the file, we need to take care not to insert before header comments - // (e.g. copyright, triple-slash references). - changeTracker.insertNodeAt(context.file, nodeToInsertBefore.getStart(), newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter }); + // (e.g. copyright, triple-slash references). Fortunately, this problem has already been solved + // for imports. + const insertionPos = getSourceFileImportLocation(file); + changeTracker.insertNodeAt(context.file, insertionPos, newVariableStatement, { + prefix: insertionPos === 0 ? undefined : context.newLineCharacter, + suffix: isLineBreak(file.text.charCodeAt(insertionPos)) ? context.newLineCharacter : context.newLineCharacter + context.newLineCharacter + }); } else { changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter }); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index f367c48ac6f..ee74a8680eb 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1332,4 +1332,26 @@ namespace ts { export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false); } + + export function getSourceFileImportLocation(node: SourceFile) { + // For a source file, it is possible there are detached comments we should not skip + const text = node.text; + let ranges = getLeadingCommentRanges(text, 0); + if (!ranges) return 0; + let position = 0; + // However we should still skip a pinned comment at the top + if (ranges.length && ranges[0].kind === SyntaxKind.MultiLineCommentTrivia && isPinnedComment(text, ranges[0])) { + position = ranges[0].end + 1; + ranges = ranges.slice(1); + } + // As well as any triple slash references + for (const range of ranges) { + if (range.kind === SyntaxKind.SingleLineCommentTrivia && isRecognizedTripleSlashComment(node.text, range.pos, range.end)) { + position = range.end + 1; + continue; + } + break; + } + return position; + } } diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js index 763473d49cd..24397bc9743 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js @@ -9,8 +9,8 @@ const x = 2 + 1; /*! Copyright */ -/* About x */ const newLocal = 2 + 1; +/* About x */ const x = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts index 763473d49cd..24397bc9743 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts @@ -9,8 +9,8 @@ const x = 2 + 1; /*! Copyright */ -/* About x */ const newLocal = 2 + 1; +/* About x */ const x = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js index 19fd25382e8..c019c085634 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js @@ -17,7 +17,6 @@ function F() { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; function F() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts index 19fd25382e8..c019c085634 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts @@ -17,7 +17,6 @@ function F() { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; function F() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js index a19bd6afa28..c6019846fbe 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js @@ -30,7 +30,6 @@ function F0() { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; function F0() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts index a19bd6afa28..c6019846fbe 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts @@ -30,7 +30,6 @@ function F0() { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; function F0() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js index 06a665140d9..0c81cb756a1 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js @@ -5,7 +5,6 @@ class C { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; class C { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts index a34b0a9ecc3..44a3456fc23 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts @@ -13,7 +13,6 @@ class C { } // ==SCOPE::Extract to constant in global scope== - const newLocal = 2 + 1; class C { From bcc93f2c086509fe9e73bc779495eee049f1c4ac Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 29 Sep 2017 18:00:27 -0700 Subject: [PATCH 130/144] Loosen restriction on usages in innermost scope Now that we're smarter about where we declare the extracted local, we can ignore usage problems in the innermost script - it'll always have access to the same symbols as the extracted expression. --- src/harness/unittests/extractConstants.ts | 17 +---------------- src/services/refactors/extractSymbol.ts | 5 ++++- .../extractConstant_BlockScopeMismatch.js | 18 ++++++++++++++++++ .../extractConstant_BlockScopeMismatch.ts | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js create mode 100644 tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index d37f9b2036c..179affd86c9 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -91,13 +91,6 @@ namespace ts { // export const y = [#|j * j|]; // }`); - testExtractConstantFailed("extractConstant_BlockScopes_Dependencies", - `for (let i = 0; i < 10; i++) { - for (let j = 0; j < 10; j++) { - let x = [#|i + 1|]; - } -}`); - testExtractConstant("extractConstant_VariableList_const", `const a = 1, b = [#|a + 1|];`); @@ -110,10 +103,7 @@ namespace ts { `const /*About A*/a = 1, /*About B*/b = [#|a + 1|];`); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. - // `i` doesn't bind in the target scope (file-level), so the extraction is disallowed. - // TODO (17098): should probably allow extraction into the same scope - testExtractConstantFailed("extractConstant_BlockScopeMismatch", ` + testExtractConstant("extractConstant_BlockScopeMismatch", ` for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const x = [#|i + 1|]; @@ -205,7 +195,6 @@ const x = [#|2 + 1|]; const x = [#|2 + 1|]; `); - // NOTE: this test isn't normative - it just documents our sub-optimal behavior. testExtractConstant("extractConstant_PinnedCommentAndDocComment", ` /*! Copyright */ @@ -217,8 +206,4 @@ const x = [#|2 + 1|]; function testExtractConstant(caption: string, text: string) { testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant); } - - function testExtractConstantFailed(caption: string, text: string) { - testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant); - } } diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 549bf653e9d..352ab91c6b9 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1349,7 +1349,10 @@ namespace ts.refactor.extractSymbol { for (let i = 0; i < scopes.length; i++) { if (!isReadonlyArray(targetRange.range)) { const scopeUsages = usagesPerScope[i]; - if (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0) { + // Special case: in the innermost scope, all usages are available. + // (The computed value reflects the value at the top-level of the scope, but the + // local will actually be declared at the same level as the extracted expression). + if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) { constantErrorsPerScope[i].push(createDiagnosticForNode(targetRange.range, Messages.CannotAccessVariablesFromNestedScopes)); } } diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js new file mode 100644 index 00000000000..64ec315b393 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const x = i + 1; + } +} + +// ==SCOPE::Extract to constant in global scope== + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts new file mode 100644 index 00000000000..64ec315b393 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts @@ -0,0 +1,18 @@ +// ==ORIGINAL== + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const x = i + 1; + } +} + +// ==SCOPE::Extract to constant in global scope== + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const newLocal = i + 1; + + const x = /*RENAME*/newLocal; + } +} + \ No newline at end of file From 835153ba50f08801ea507686721eead349a560a8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 2 Oct 2017 12:22:05 -0700 Subject: [PATCH 131/144] PR feedback --- src/compiler/builder.ts | 2 +- src/compiler/program.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 9436be6e8a1..b2cdad3598a 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -521,7 +521,7 @@ namespace ts { } // Return array of values that needs emit - return arrayFrom(seenFileNamesMap.values()); + return flatMapIter(seenFileNamesMap.values(), value => value); } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index d335ef3df85..ee1e2ea28fe 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -764,8 +764,7 @@ namespace ts { for (let i = 0; i < moduleNames.length; i++) { const moduleName = moduleNames[i]; - // If we want to reuse resolutions more aggressively, we can refine this to check for whether the - // text of the corresponding modulenames has changed. + // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { const oldResolvedModule = oldSourceFile && oldSourceFile.resolvedModules.get(moduleName); if (oldResolvedModule) { From bf75a3f4ac391bd4c928b84d5a660c89151dfc5e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 2 Oct 2017 12:54:04 -0700 Subject: [PATCH 132/144] Emit .d.ts file in test --- .../baselines/reference/strictFunctionTypes1.js | 17 +++++++++++++++++ tests/cases/compiler/strictFunctionTypes1.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/tests/baselines/reference/strictFunctionTypes1.js b/tests/baselines/reference/strictFunctionTypes1.js index 28a7a7f61d6..e802c443561 100644 --- a/tests/baselines/reference/strictFunctionTypes1.js +++ b/tests/baselines/reference/strictFunctionTypes1.js @@ -23,3 +23,20 @@ var x1 = f1(fo, fs); // (x: string) => void var x2 = f2("abc", fo, fs); // "abc" var x3 = f3("abc", fo, fx); // "abc" | "def" var x4 = f4(fo, fs); // Func + + +//// [strictFunctionTypes1.d.ts] +declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; +declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; +declare function f3(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void): T; +interface Func { + (x: T): void; +} +declare function f4(f1: Func, f2: Func): Func; +declare function fo(x: Object): void; +declare function fs(x: string): void; +declare function fx(f: (x: "def") => void): void; +declare const x1: (x: string) => void; +declare const x2 = "abc"; +declare const x3: string; +declare const x4: Func; diff --git a/tests/cases/compiler/strictFunctionTypes1.ts b/tests/cases/compiler/strictFunctionTypes1.ts index 7d8ffc689ff..6c07bc51ffe 100644 --- a/tests/cases/compiler/strictFunctionTypes1.ts +++ b/tests/cases/compiler/strictFunctionTypes1.ts @@ -1,4 +1,5 @@ // @strict: true +// @declaration: true declare function f1(f1: (x: T) => void, f2: (x: T) => void): (x: T) => void; declare function f2(obj: T, f1: (x: T) => void, f2: (x: T) => void): T; From eefe5c970602a6bf90c92046e7423c8e99cbff27 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 2 Oct 2017 13:26:35 -0700 Subject: [PATCH 133/144] Fix acquiring format options for getEditsForRefactor (#18848) * Fix acquiring format options for getEditsForRefactor * Add test * Fix test description * Use `executeCommandSeq` --- .../unittests/tsserverProjectSystem.ts | 56 +++++++++++++++++++ src/server/client.ts | 3 +- src/server/protocol.ts | 1 - src/server/session.ts | 2 +- .../convertFunctionToEs6Class-server.ts | 17 +++--- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 651dbae7142..dcefa14bcb8 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4245,4 +4245,60 @@ namespace ts.projectSystem { } }); }); + + describe("refactors", () => { + it("use formatting options", () => { + const file = { + path: "/a.ts", + content: "function f() {\n 1;\n}", + }; + const host = createServerHost([file]); + const session = createSession(host); + openFilesForSession([file], session); + + const response0 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.Configure, + arguments: { + formatOptions: { + indentSize: 2, + }, + }, + }).response; + assert.deepEqual(response0, /*expected*/ undefined); + + const response1 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + refactor: "Extract Symbol", + action: "function_scope_1", + file: "/a.ts", + startLine: 2, + startOffset: 3, + endLine: 2, + endOffset: 4, + }, + }).response; + assert.deepEqual(response1, { + edits: [ + { + fileName: "/a.ts", + textChanges: [ + { + start: { line: 2, offset: 1 }, + end: { line: 3, offset: 1 }, + newText: " newFunction();\n", + }, + { + start: { line: 3, offset: 2 }, + end: { line: 3, offset: 2 }, + newText: "\nfunction newFunction() {\n 1;\n}\n", + }, + ] + } + ], + renameFilename: "/a.ts", + renameLocation: { line: 2, offset: 3 }, + }); + }); + }); } diff --git a/src/server/client.ts b/src/server/client.ts index 9e472a83e2b..0ffac42dae4 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -573,7 +573,7 @@ namespace ts.server { getEditsForRefactor( fileName: string, - formatOptions: FormatCodeSettings, + _formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo { @@ -581,7 +581,6 @@ namespace ts.server { const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; args.refactor = refactorName; args.action = actionName; - args.formatOptions = formatOptions; const request = this.processRequest(CommandNames.GetEditsForRefactor, args); const response = this.processResponse(request); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 44862c738cc..3fdbd8fd7f7 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -494,7 +494,6 @@ namespace ts.server.protocol { refactor: string; /* The 'name' property from the refactoring action */ action: string; - formatOptions?: FormatCodeSettings, }; diff --git a/src/server/session.ts b/src/server/session.ts index 02eb76eeae8..5d71d1b11f9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1488,7 +1488,7 @@ namespace ts.server { const result = project.getLanguageService().getEditsForRefactor( file, - args.formatOptions ? convertFormatOptions(args.formatOptions) : this.projectService.getFormatCodeOptions(), + this.projectService.getFormatCodeOptions(file), position || textRange, args.refactor, args.action diff --git a/tests/cases/fourslash/server/convertFunctionToEs6Class-server.ts b/tests/cases/fourslash/server/convertFunctionToEs6Class-server.ts index 437bf6dadf4..e7da28a2770 100644 --- a/tests/cases/fourslash/server/convertFunctionToEs6Class-server.ts +++ b/tests/cases/fourslash/server/convertFunctionToEs6Class-server.ts @@ -14,13 +14,14 @@ verify.applicableRefactorAvailableAtMarker('1'); // NOTE: '// Comment' should be included, but due to incorrect handling of trivia, // it's omitted right now. +// TODO: GH#18445 verify.fileAfterApplyingRefactorAtMarker('1', -`class fn { - constructor() { - this.baz = 10; - } - bar() { - console.log('hello world'); - } -} +`class fn {\r + constructor() {\r + this.baz = 10;\r + }\r + bar() {\r + console.log('hello world');\r + }\r +}\r `, 'Convert to ES2015 class', 'convert'); From bff843a9c9def253257a0290cb590d4e89897e25 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 2 Oct 2017 13:39:55 -0700 Subject: [PATCH 134/144] Improve error elaboration for invariant generic types --- src/compiler/checker.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index de1b28f6a56..92abee8b9d1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9415,7 +9415,16 @@ namespace ts { // with a structural comparison. Otherwise, we know for certain the instantiations aren't // related and we can return here. if (variances !== emptyArray && !hasCovariantVoidArgument(target, variances)) { - return Ternary.False; + // In some cases generic types that are covariant in regular type checking mode become + // invariant in --strictFunctionTypes mode because one or more type parameters are used in + // both co- and contravariant positions. In order to make it easier to diagnose *why* such + // types are invariant, if any of the type parameters are invariant we reset the reported + // errors and instead force a structural comparison (which will include elaborations that + // reveal the reason). + if (!(reportErrors && some(variances, v => v === Variance.Invariant))) { + return Ternary.False; + } + errorInfo = saveErrorInfo; } } // Even if relationship doesn't hold for unions, intersections, or generic type references, From c2344e07a4d02d5cc453c6e7cc5eda6d0d373de6 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 2 Oct 2017 13:40:26 -0700 Subject: [PATCH 135/144] Add error elaboration test --- .../strictFunctionTypesErrors.errors.txt | 35 ++++++++++++++++- .../reference/strictFunctionTypesErrors.js | 18 +++++++++ .../strictFunctionTypesErrors.symbols | 36 ++++++++++++++++++ .../reference/strictFunctionTypesErrors.types | 38 +++++++++++++++++++ .../compiler/strictFunctionTypesErrors.ts | 15 ++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/strictFunctionTypesErrors.errors.txt b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt index eb0c2d00233..2827983bd5a 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.errors.txt +++ b/tests/baselines/reference/strictFunctionTypesErrors.errors.txt @@ -79,9 +79,17 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(84,1): error TS2322: Type 'Fun tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Comparer2' is not assignable to type 'Comparer2'. Type 'Animal' is not assignable to type 'Dog'. Property 'dog' is missing in type 'Animal'. +tests/cases/compiler/strictFunctionTypesErrors.ts(126,1): error TS2322: Type 'Crate' is not assignable to type 'Crate'. + Types of property 'onSetItem' are incompatible. + Type '(item: Dog) => void' is not assignable to type '(item: Animal) => void'. + Types of parameters 'item' and 'item' are incompatible. + Type 'Animal' is not assignable to type 'Dog'. +tests/cases/compiler/strictFunctionTypesErrors.ts(127,1): error TS2322: Type 'Crate' is not assignable to type 'Crate'. + Types of property 'item' are incompatible. + Type 'Animal' is not assignable to type 'Dog'. -==== tests/cases/compiler/strictFunctionTypesErrors.ts (29 errors) ==== +==== tests/cases/compiler/strictFunctionTypesErrors.ts (31 errors) ==== export {} @@ -304,4 +312,29 @@ tests/cases/compiler/strictFunctionTypesErrors.ts(111,1): error TS2322: Type 'Co !!! error TS2322: Type 'Animal' is not assignable to type 'Dog'. !!! error TS2322: Property 'dog' is missing in type 'Animal'. dogComparer2 = animalComparer2; // Ok + + // Crate is invariant in --strictFunctionTypes mode + + interface Crate { + item: T; + onSetItem: (item: T) => void; + } + + declare let animalCrate: Crate; + declare let dogCrate: Crate; + + // Errors below should elaborate the reason for invariance + + animalCrate = dogCrate; // Error + ~~~~~~~~~~~ +!!! error TS2322: Type 'Crate' is not assignable to type 'Crate'. +!!! error TS2322: Types of property 'onSetItem' are incompatible. +!!! error TS2322: Type '(item: Dog) => void' is not assignable to type '(item: Animal) => void'. +!!! error TS2322: Types of parameters 'item' and 'item' are incompatible. +!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'. + dogCrate = animalCrate; // Error + ~~~~~~~~ +!!! error TS2322: Type 'Crate' is not assignable to type 'Crate'. +!!! error TS2322: Types of property 'item' are incompatible. +!!! error TS2322: Type 'Animal' is not assignable to type 'Dog'. \ No newline at end of file diff --git a/tests/baselines/reference/strictFunctionTypesErrors.js b/tests/baselines/reference/strictFunctionTypesErrors.js index 0049fe970ce..2be598f0ef9 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.js +++ b/tests/baselines/reference/strictFunctionTypesErrors.js @@ -111,6 +111,21 @@ declare let dogComparer2: Comparer2; animalComparer2 = dogComparer2; // Error dogComparer2 = animalComparer2; // Ok + +// Crate is invariant in --strictFunctionTypes mode + +interface Crate { + item: T; + onSetItem: (item: T) => void; +} + +declare let animalCrate: Crate; +declare let dogCrate: Crate; + +// Errors below should elaborate the reason for invariance + +animalCrate = dogCrate; // Error +dogCrate = animalCrate; // Error //// [strictFunctionTypesErrors.js] @@ -168,3 +183,6 @@ animalComparer1 = dogComparer1; // Ok dogComparer1 = animalComparer1; // Ok animalComparer2 = dogComparer2; // Error dogComparer2 = animalComparer2; // Ok +// Errors below should elaborate the reason for invariance +animalCrate = dogCrate; // Error +dogCrate = animalCrate; // Error diff --git a/tests/baselines/reference/strictFunctionTypesErrors.symbols b/tests/baselines/reference/strictFunctionTypesErrors.symbols index ce3a81f524f..30faf83d87e 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.symbols +++ b/tests/baselines/reference/strictFunctionTypesErrors.symbols @@ -364,3 +364,39 @@ dogComparer2 = animalComparer2; // Ok >dogComparer2 : Symbol(dogComparer2, Decl(strictFunctionTypesErrors.ts, 108, 11)) >animalComparer2 : Symbol(animalComparer2, Decl(strictFunctionTypesErrors.ts, 107, 11)) +// Crate is invariant in --strictFunctionTypes mode + +interface Crate { +>Crate : Symbol(Crate, Decl(strictFunctionTypesErrors.ts, 111, 31)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 115, 16)) + + item: T; +>item : Symbol(Crate.item, Decl(strictFunctionTypesErrors.ts, 115, 20)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 115, 16)) + + onSetItem: (item: T) => void; +>onSetItem : Symbol(Crate.onSetItem, Decl(strictFunctionTypesErrors.ts, 116, 12)) +>item : Symbol(item, Decl(strictFunctionTypesErrors.ts, 117, 16)) +>T : Symbol(T, Decl(strictFunctionTypesErrors.ts, 115, 16)) +} + +declare let animalCrate: Crate; +>animalCrate : Symbol(animalCrate, Decl(strictFunctionTypesErrors.ts, 120, 11)) +>Crate : Symbol(Crate, Decl(strictFunctionTypesErrors.ts, 111, 31)) +>Animal : Symbol(Animal, Decl(strictFunctionTypesErrors.ts, 87, 8)) + +declare let dogCrate: Crate; +>dogCrate : Symbol(dogCrate, Decl(strictFunctionTypesErrors.ts, 121, 11)) +>Crate : Symbol(Crate, Decl(strictFunctionTypesErrors.ts, 111, 31)) +>Dog : Symbol(Dog, Decl(strictFunctionTypesErrors.ts, 89, 33)) + +// Errors below should elaborate the reason for invariance + +animalCrate = dogCrate; // Error +>animalCrate : Symbol(animalCrate, Decl(strictFunctionTypesErrors.ts, 120, 11)) +>dogCrate : Symbol(dogCrate, Decl(strictFunctionTypesErrors.ts, 121, 11)) + +dogCrate = animalCrate; // Error +>dogCrate : Symbol(dogCrate, Decl(strictFunctionTypesErrors.ts, 121, 11)) +>animalCrate : Symbol(animalCrate, Decl(strictFunctionTypesErrors.ts, 120, 11)) + diff --git a/tests/baselines/reference/strictFunctionTypesErrors.types b/tests/baselines/reference/strictFunctionTypesErrors.types index f2d3b52a5b9..e4372d4b8fa 100644 --- a/tests/baselines/reference/strictFunctionTypesErrors.types +++ b/tests/baselines/reference/strictFunctionTypesErrors.types @@ -416,3 +416,41 @@ dogComparer2 = animalComparer2; // Ok >dogComparer2 : Comparer2 >animalComparer2 : Comparer2 +// Crate is invariant in --strictFunctionTypes mode + +interface Crate { +>Crate : Crate +>T : T + + item: T; +>item : T +>T : T + + onSetItem: (item: T) => void; +>onSetItem : (item: T) => void +>item : T +>T : T +} + +declare let animalCrate: Crate; +>animalCrate : Crate +>Crate : Crate +>Animal : Animal + +declare let dogCrate: Crate; +>dogCrate : Crate +>Crate : Crate +>Dog : Dog + +// Errors below should elaborate the reason for invariance + +animalCrate = dogCrate; // Error +>animalCrate = dogCrate : Crate +>animalCrate : Crate +>dogCrate : Crate + +dogCrate = animalCrate; // Error +>dogCrate = animalCrate : Crate +>dogCrate : Crate +>animalCrate : Crate + diff --git a/tests/cases/compiler/strictFunctionTypesErrors.ts b/tests/cases/compiler/strictFunctionTypesErrors.ts index 32029364d76..fbf1c0fa6da 100644 --- a/tests/cases/compiler/strictFunctionTypesErrors.ts +++ b/tests/cases/compiler/strictFunctionTypesErrors.ts @@ -111,3 +111,18 @@ declare let dogComparer2: Comparer2; animalComparer2 = dogComparer2; // Error dogComparer2 = animalComparer2; // Ok + +// Crate is invariant in --strictFunctionTypes mode + +interface Crate { + item: T; + onSetItem: (item: T) => void; +} + +declare let animalCrate: Crate; +declare let dogCrate: Crate; + +// Errors below should elaborate the reason for invariance + +animalCrate = dogCrate; // Error +dogCrate = animalCrate; // Error From ba5f09b66d7f51ef7f54e8cf4f7893df331f98b2 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 2 Oct 2017 15:30:52 -0700 Subject: [PATCH 136/144] Localize 'in' in extraction description --- src/compiler/diagnosticMessages.json | 2 +- src/services/refactors/extractSymbol.ts | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 4465976d170..292c7f305c0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3720,7 +3720,7 @@ "code": 95003 }, - "Extract to {0}": { + "Extract to {0} in {1}": { "category": "Message", "code": 95004 }, diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 352ab91c6b9..912992be50f 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -43,7 +43,7 @@ namespace ts.refactor.extractSymbol { // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.functionDescription]); + const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.functionDescription, extraction.scopeDescription]); if (!usedFunctionNames.has(description)) { usedFunctionNames.set(description, true); functionActions.push({ @@ -58,7 +58,7 @@ namespace ts.refactor.extractSymbol { // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.constantDescription]); + const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.constantDescription, extraction.scopeDescription]); if (!usedConstantNames.has(description)) { usedConstantNames.set(description, true); constantActions.push({ @@ -547,6 +547,7 @@ namespace ts.refactor.extractSymbol { readonly functionErrors: ReadonlyArray; readonly constantDescription: string; readonly constantErrors: ReadonlyArray; + readonly scopeDescription: string; } /** * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. @@ -561,6 +562,11 @@ namespace ts.refactor.extractSymbol { functionErrors: functionErrorsPerScope[i], constantDescription: getDescriptionForConstantInScope(scope), constantErrors: constantErrorsPerScope[i], + scopeDescription: isFunctionLikeDeclaration(scope) + ? getDescriptionForFunctionLikeDeclaration(scope) + : isClassLike(scope) + ? getDescriptionForClassLikeDeclaration(scope) + : getDescriptionForModuleLikeDeclaration(scope) })); return extractions; } @@ -590,17 +596,15 @@ namespace ts.refactor.extractSymbol { function getDescriptionForFunctionInScope(scope: Scope): string { return isFunctionLikeDeclaration(scope) - ? `inner function in ${getDescriptionForFunctionLikeDeclaration(scope)}` + ? "inner function" : isClassLike(scope) - ? `method in ${getDescriptionForClassLikeDeclaration(scope)}` - : `function in ${getDescriptionForModuleLikeDeclaration(scope)}`; + ? "method" + : "function"; } function getDescriptionForConstantInScope(scope: Scope): string { - return isFunctionLikeDeclaration(scope) - ? `constant in ${getDescriptionForFunctionLikeDeclaration(scope)}` - : isClassLike(scope) - ? `readonly field in ${getDescriptionForClassLikeDeclaration(scope)}` - : `constant in ${getDescriptionForModuleLikeDeclaration(scope)}`; + return isClassLike(scope) + ? "readonly field" + : "constant"; } function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { switch (scope.kind) { From 724f71cae18260d23ddc88e187070dc3a90d25d9 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 2 Oct 2017 15:57:36 -0700 Subject: [PATCH 137/144] Describe the innermost scope as "enclosing scope" for constants Using the same description as for functions was confusing because the extracted constant was inserted below the top-level of that scope. --- src/services/refactors/extractSymbol.ts | 29 ++++++++++++------- .../extractConstant_BlockScopeMismatch.js | 2 +- .../extractConstant_BlockScopeMismatch.ts | 2 +- ...ractConstant_BlockScopes_NoDependencies.js | 2 +- ...ractConstant_BlockScopes_NoDependencies.ts | 2 +- ...extractConstant_ClassInsertionPosition1.js | 2 +- ...extractConstant_ClassInsertionPosition1.ts | 2 +- ...extractConstant_ClassInsertionPosition2.js | 2 +- ...extractConstant_ClassInsertionPosition2.ts | 2 +- ...extractConstant_ClassInsertionPosition3.js | 2 +- ...extractConstant_ClassInsertionPosition3.ts | 2 +- .../extractConstant_Directive.js | 2 +- .../extractConstant_Directive.ts | 2 +- .../extractConstant_ExpressionStatement.js | 2 +- .../extractConstant_ExpressionStatement.ts | 2 +- ...tConstant_ExpressionStatementExpression.js | 2 +- ...tConstant_ExpressionStatementExpression.ts | 2 +- .../extractConstant_Function.js | 2 +- .../extractConstant_Function.ts | 2 +- .../extractConstant/extractConstant_Method.js | 2 +- .../extractConstant/extractConstant_Method.ts | 2 +- .../extractConstant_MultipleHeaders.js | 2 +- .../extractConstant_MultipleHeaders.ts | 2 +- .../extractConstant_Namespace.ts | 2 +- .../extractConstant_Parameters.js | 2 +- .../extractConstant_Parameters.ts | 2 +- .../extractConstant_PinnedComment.js | 2 +- .../extractConstant_PinnedComment.ts | 2 +- ...ractConstant_PinnedCommentAndDocComment.js | 2 +- ...ractConstant_PinnedCommentAndDocComment.ts | 2 +- ...actConstant_StatementInsertionPosition1.js | 2 +- ...actConstant_StatementInsertionPosition1.ts | 2 +- ...actConstant_StatementInsertionPosition2.js | 2 +- ...actConstant_StatementInsertionPosition2.ts | 2 +- ...actConstant_StatementInsertionPosition3.js | 2 +- ...actConstant_StatementInsertionPosition3.ts | 2 +- ...actConstant_StatementInsertionPosition4.js | 2 +- ...actConstant_StatementInsertionPosition4.ts | 2 +- ...actConstant_StatementInsertionPosition5.js | 2 +- ...actConstant_StatementInsertionPosition5.ts | 2 +- ...actConstant_StatementInsertionPosition7.js | 2 +- ...actConstant_StatementInsertionPosition7.ts | 2 +- .../extractConstant_TopLevel.js | 2 +- .../extractConstant_TopLevel.ts | 2 +- .../extractConstant_TripleSlash.js | 2 +- .../extractConstant_TripleSlash.ts | 2 +- .../extractConstant_TypeParameters.ts | 2 +- ...ractConstant_VariableList_MultipleLines.js | 2 +- ...ractConstant_VariableList_MultipleLines.ts | 2 +- .../extractConstant_VariableList_const.js | 2 +- .../extractConstant_VariableList_const.ts | 2 +- .../extractConstant_VariableList_let.js | 2 +- .../extractConstant_VariableList_let.ts | 2 +- 53 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 912992be50f..c72c0ca76d3 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -43,7 +43,7 @@ namespace ts.refactor.extractSymbol { // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.functionDescription, extraction.scopeDescription]); + const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.functionDescription, extraction.functionScopeDescription]); if (!usedFunctionNames.has(description)) { usedFunctionNames.set(description, true); functionActions.push({ @@ -58,7 +58,7 @@ namespace ts.refactor.extractSymbol { // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.constantDescription, extraction.scopeDescription]); + const description = formatStringFromArgs(Diagnostics.Extract_to_0_in_1.message, [extraction.constantDescription, extraction.constantScopeDescription]); if (!usedConstantNames.has(description)) { usedConstantNames.set(description, true); constantActions.push({ @@ -544,10 +544,11 @@ namespace ts.refactor.extractSymbol { interface PossibleExtraction { readonly functionDescription: string; + readonly functionScopeDescription: string; readonly functionErrors: ReadonlyArray; readonly constantDescription: string; + readonly constantScopeDescription: string; readonly constantErrors: ReadonlyArray; - readonly scopeDescription: string; } /** * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. @@ -557,17 +558,23 @@ namespace ts.refactor.extractSymbol { function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): ReadonlyArray | undefined { const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 - const extractions = scopes.map((scope, i): PossibleExtraction => ({ - functionDescription: getDescriptionForFunctionInScope(scope), - functionErrors: functionErrorsPerScope[i], - constantDescription: getDescriptionForConstantInScope(scope), - constantErrors: constantErrorsPerScope[i], - scopeDescription: isFunctionLikeDeclaration(scope) + const extractions = scopes.map((scope, i): PossibleExtraction => { + const scopeDescription = isFunctionLikeDeclaration(scope) ? getDescriptionForFunctionLikeDeclaration(scope) : isClassLike(scope) ? getDescriptionForClassLikeDeclaration(scope) - : getDescriptionForModuleLikeDeclaration(scope) - })); + : getDescriptionForModuleLikeDeclaration(scope); + return { + functionDescription: getDescriptionForFunctionInScope(scope), + functionErrors: functionErrorsPerScope[i], + functionScopeDescription: scopeDescription, + constantDescription: getDescriptionForConstantInScope(scope), + constantErrors: constantErrorsPerScope[i], + constantScopeDescription: (i === 0 && !isClassLike(scope)) + ? "enclosing scope" // Like "global scope" and "module scope", this is not localized. + : scopeDescription, + }; + }); return extractions; } diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js index 64ec315b393..65871513e18 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.js @@ -6,7 +6,7 @@ for (let i = 0; i < 10; i++) { } } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts index 64ec315b393..65871513e18 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopeMismatch.ts @@ -6,7 +6,7 @@ for (let i = 0; i < 10; i++) { } } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js index b934af59550..4446502920a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js @@ -4,7 +4,7 @@ for (let i = 0; i < 10; i++) { let x = 1; } } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts index b934af59550..4446502920a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts @@ -4,7 +4,7 @@ for (let i = 0; i < 10; i++) { let x = 1; } } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js index 4e7bcc0521a..6fa63a655f5 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { a = 1; b = 2; diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts index e024fda34bc..15c5d22a34b 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { a = 1; b = 2; diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js index 137fc5ff0bb..3273cfbeaa0 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { a = 1; M1() { } diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts index 5bd239c0e63..777173b0dcd 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { a = 1; M1() { } diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js index 1679e46d8d5..b990075166e 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { M1() { } a = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts index 4cc44565fa0..516a24b8b20 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts @@ -8,7 +8,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M3== +// ==SCOPE::Extract to constant in enclosing scope== class C { M1() { } a = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.js b/tests/baselines/reference/extractConstant/extractConstant_Directive.js index 5ba48e27de9..85b9ff76200 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Directive.js +++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.js @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== "strict"; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.ts b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts index 5ba48e27de9..85b9ff76200 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Directive.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== "strict"; diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.js b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.js index 6bf35cd17e1..c3ccdde3b4f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.js +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.js @@ -1,4 +1,4 @@ // ==ORIGINAL== "hello"; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts index 6bf35cd17e1..c3ccdde3b4f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts @@ -1,4 +1,4 @@ // ==ORIGINAL== "hello"; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.js b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.js index 6bf35cd17e1..c3ccdde3b4f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.js +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.js @@ -1,4 +1,4 @@ // ==ORIGINAL== "hello"; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts index 6bf35cd17e1..c3ccdde3b4f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts @@ -1,4 +1,4 @@ // ==ORIGINAL== "hello"; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Function.js b/tests/baselines/reference/extractConstant/extractConstant_Function.js index 67c8255c4b4..cff41806566 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Function.js +++ b/tests/baselines/reference/extractConstant/extractConstant_Function.js @@ -2,7 +2,7 @@ function F() { let x = 1; } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Function.ts b/tests/baselines/reference/extractConstant/extractConstant_Function.ts index 67c8255c4b4..cff41806566 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Function.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_Function.ts @@ -2,7 +2,7 @@ function F() { let x = 1; } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Method.js b/tests/baselines/reference/extractConstant/extractConstant_Method.js index bd7c7f86359..32b74d00576 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Method.js +++ b/tests/baselines/reference/extractConstant/extractConstant_Method.js @@ -4,7 +4,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M== +// ==SCOPE::Extract to constant in enclosing scope== class C { M() { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Method.ts b/tests/baselines/reference/extractConstant/extractConstant_Method.ts index 1ae4c8b1cb5..5514b702597 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Method.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_Method.ts @@ -4,7 +4,7 @@ class C { let x = 1; } } -// ==SCOPE::Extract to constant in method 'M== +// ==SCOPE::Extract to constant in enclosing scope== class C { M() { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js index d2d0f14c01e..2ccf5932d38 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js +++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js @@ -8,7 +8,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts index d2d0f14c01e..2ccf5932d38 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts @@ -8,7 +8,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts b/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts index 8f25847165f..81a543d29c4 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts @@ -2,7 +2,7 @@ namespace N { let x = 1; } -// ==SCOPE::Extract to constant in namespace 'N'== +// ==SCOPE::Extract to constant in enclosing scope== namespace N { const newLocal = 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Parameters.js b/tests/baselines/reference/extractConstant/extractConstant_Parameters.js index e6c9c474fbc..b90f78d96af 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Parameters.js +++ b/tests/baselines/reference/extractConstant/extractConstant_Parameters.js @@ -3,7 +3,7 @@ function F() { let w = 1; let x = w + 1; } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { let w = 1; const newLocal = w + 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts b/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts index e6c9c474fbc..b90f78d96af 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts @@ -3,7 +3,7 @@ function F() { let w = 1; let x = w + 1; } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { let w = 1; const newLocal = w + 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js index a372457fc48..cc897b5c6b9 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts index a372457fc48..cc897b5c6b9 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js index 24397bc9743..9ee07c7cd6a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js @@ -5,7 +5,7 @@ /* About x */ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts index 24397bc9743..9ee07c7cd6a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts @@ -5,7 +5,7 @@ /* About x */ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /*! Copyright */ diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js index f881a021232..2197dd13e3a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js @@ -5,7 +5,7 @@ for (let j = 0; j < 10; j++) { const x = i + 1; } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts index f881a021232..2197dd13e3a 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts @@ -5,7 +5,7 @@ for (let j = 0; j < 10; j++) { const x = i + 1; } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js index 7ffd89161a2..900798c19ab 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js @@ -7,7 +7,7 @@ function F() { } } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; function F() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts index 7ffd89161a2..900798c19ab 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts @@ -7,7 +7,7 @@ function F() { } } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; function F() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js index 6ba0b2dfaae..3f2f7089ef3 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js @@ -4,7 +4,7 @@ for (let j = 0; j < 10; j++) { const x = 2 + 1; } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let j = 0; j < 10; j++) { const newLocal = 2 + 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts index 6ba0b2dfaae..3f2f7089ef3 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts @@ -4,7 +4,7 @@ for (let j = 0; j < 10; j++) { const x = 2 + 1; } -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== for (let j = 0; j < 10; j++) { const newLocal = 2 + 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js index c019c085634..4c7c2a50217 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js @@ -6,7 +6,7 @@ function F() { } } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts index c019c085634..4c7c2a50217 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts @@ -6,7 +6,7 @@ function F() { } } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F() { for (let j = 0; j < 10; j++) { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js index c6019846fbe..4b9647fa445 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js @@ -7,7 +7,7 @@ function F0() { } } -// ==SCOPE::Extract to constant in function 'F1'== +// ==SCOPE::Extract to constant in enclosing scope== function F0() { function F1() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts index c6019846fbe..4b9647fa445 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts @@ -7,7 +7,7 @@ function F0() { } } -// ==SCOPE::Extract to constant in function 'F1'== +// ==SCOPE::Extract to constant in enclosing scope== function F0() { function F1() { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js index f73cba1b34c..f23db0c4aa9 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js @@ -9,7 +9,7 @@ class C { } } -// ==SCOPE::Extract to constant in method 'M== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; class C { diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts index 308df5785fb..6867685d6db 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts @@ -9,7 +9,7 @@ class C { } } -// ==SCOPE::Extract to constant in method 'M== +// ==SCOPE::Extract to constant in enclosing scope== const i = 0; class C { diff --git a/tests/baselines/reference/extractConstant/extractConstant_TopLevel.js b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.js index fb0447583ff..fc83d32123e 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_TopLevel.js +++ b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.js @@ -1,6 +1,6 @@ // ==ORIGINAL== let x = 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const newLocal = 1; let x = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts index fb0447583ff..fc83d32123e 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts @@ -1,6 +1,6 @@ // ==ORIGINAL== let x = 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const newLocal = 1; let x = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js index 5003e8aa33e..e5f87e5d8b8 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js +++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /// diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts index 5003e8aa33e..e5f87e5d8b8 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts @@ -4,7 +4,7 @@ const x = 2 + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== /// diff --git a/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts b/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts index a323e988176..f9367ab787c 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts @@ -2,7 +2,7 @@ function F(t: T) { let x = t + 1; } -// ==SCOPE::Extract to constant in function 'F'== +// ==SCOPE::Extract to constant in enclosing scope== function F(t: T) { const newLocal = t + 1; diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js index 7582195a458..18bb4afc28f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js @@ -1,6 +1,6 @@ // ==ORIGINAL== const /*About A*/a = 1, /*About B*/b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*About A*/a = 1, /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts index 7582195a458..18bb4afc28f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts @@ -1,6 +1,6 @@ // ==ORIGINAL== const /*About A*/a = 1, /*About B*/b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const /*About A*/a = 1, /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js index efcc7a5a00c..161e5a6a02f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js @@ -1,4 +1,4 @@ // ==ORIGINAL== const a = 1, b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts index efcc7a5a00c..161e5a6a02f 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts @@ -1,4 +1,4 @@ // ==ORIGINAL== const a = 1, b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js index dc0796596cb..bc05b13ba7e 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js @@ -1,4 +1,4 @@ // ==ORIGINAL== let a = 1, b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts index dc0796596cb..bc05b13ba7e 100644 --- a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts +++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts @@ -1,4 +1,4 @@ // ==ORIGINAL== let a = 1, b = a + 1; -// ==SCOPE::Extract to constant in global scope== +// ==SCOPE::Extract to constant in enclosing scope== let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal; \ No newline at end of file From 7f969e8138a468e932b54f15f26fd957a8a3a6f3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 2 Oct 2017 16:14:42 -0700 Subject: [PATCH 138/144] Making APIs as internal so that we can enable them after we have figured out final details --- src/compiler/builder.ts | 29 ++-- src/compiler/program.ts | 1 + src/compiler/types.ts | 2 + src/compiler/watch.ts | 3 +- src/server/builder.ts | 359 ---------------------------------------- src/server/project.ts | 6 +- 6 files changed, 22 insertions(+), 378 deletions(-) delete mode 100644 src/server/builder.ts diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index b2cdad3598a..baa88e0d20f 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -18,18 +18,12 @@ namespace ts { text: string; } - export interface ChangedProgramFiles { - /** Minimal set of list of files that require emit */ - readonly filesToEmit: ReadonlyArray; - /** File paths of source files changed/added/removed or affected by changed files */ - readonly changedFiles: ReadonlyArray; - } - + /* @internal */ export interface Builder { /** - * This is the callback when file infos in the builder are updated + * Call this to feed new program */ - onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution): void; + updateProgram(newProgram: Program): void; getFilesAffectedBy(program: Program, path: Path): string[]; emitFile(program: Program, path: Path): EmitOutput; @@ -97,6 +91,7 @@ namespace ts { signature: string; } + /* @internal */ export function createBuilder( getCanonicalFileName: (fileName: string) => string, getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed, @@ -110,7 +105,7 @@ namespace ts { const changedFileNames = createMap(); let emitHandler: EmitHandler; return { - onProgramUpdateGraph, + updateProgram, getFilesAffectedBy, emitFile, emitChangedFiles, @@ -118,7 +113,7 @@ namespace ts { clear }; - function createProgramGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) { + function createProgramGraph(program: Program) { const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None; if (isModuleEmit !== currentIsModuleEmit) { isModuleEmit = currentIsModuleEmit; @@ -135,7 +130,7 @@ namespace ts { // Remove existing file info onDeleteValue: removeExistingFileInfo, // We will update in place instead of deleting existing value and adding new one - onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) + onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) } ); } @@ -157,13 +152,13 @@ namespace ts { emitHandler.onRemoveSourceFile(path); } - function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) { + function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { if (existingInfo.version !== sourceFile.version) { registerChangedFile(sourceFile.path, sourceFile.fileName); existingInfo.version = sourceFile.version; emitHandler.onUpdateSourceFile(program, sourceFile); } - else if (hasInvalidatedResolution(sourceFile.path) && + else if (program.hasInvalidatedResolution(sourceFile.path) && emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) { registerChangedFile(sourceFile.path, sourceFile.fileName); } @@ -171,13 +166,13 @@ namespace ts { function ensureProgramGraph(program: Program) { if (!emitHandler) { - createProgramGraph(program, returnFalse); + createProgramGraph(program); } } - function onProgramUpdateGraph(program: Program, hasInvalidatedResolution: HasInvalidatedResolution) { + function updateProgram(newProgram: Program) { if (emitHandler) { - createProgramGraph(program, hasInvalidatedResolution); + createProgramGraph(newProgram); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ee1e2ea28fe..36bb0a73729 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -663,6 +663,7 @@ namespace ts { getSourceFileFromReference, sourceFileToPackageName, redirectTargetsSet, + hasInvalidatedResolution }; verifyCompilerOptions(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d56aa12c4ad..f09041a3d2c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2525,6 +2525,8 @@ namespace ts { /* @internal */ sourceFileToPackageName: Map; /** Set of all source files that some other source file redirects to. */ /* @internal */ redirectTargetsSet: Map; + /** Returns true when file in the program had invalidated resolution at the time of program creation. */ + hasInvalidatedResolution: HasInvalidatedResolution; } /* @internal */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 8b098f41098..2900737b09f 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -2,6 +2,7 @@ /// /// +/* @internal */ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; @@ -341,7 +342,7 @@ namespace ts { compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); resolutionCache.finishCachingPerDirectoryResolution(); - builder.onProgramUpdateGraph(program, hasInvalidatedResolution); + builder.updateProgram(program); // Update watches updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); diff --git a/src/server/builder.ts b/src/server/builder.ts deleted file mode 100644 index 5cf65611fb3..00000000000 --- a/src/server/builder.ts +++ /dev/null @@ -1,359 +0,0 @@ -/// -/// -/// - -namespace ts.server { - - export function shouldEmitFile(scriptInfo: ScriptInfo) { - return !scriptInfo.hasMixedContent && !scriptInfo.isDynamic; - } - - /** - * An abstract file info that maintains a shape signature. - */ - export class BuilderFileInfo { - - private lastCheckedShapeSignature: string; - - constructor(public readonly scriptInfo: ScriptInfo, public readonly project: Project) { - } - - public isExternalModuleOrHasOnlyAmbientExternalModules() { - const sourceFile = this.getSourceFile(); - return isExternalModule(sourceFile) || this.containsOnlyAmbientModules(sourceFile); - } - - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - private containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (statement.kind !== SyntaxKind.ModuleDeclaration || (statement).name.kind !== SyntaxKind.StringLiteral) { - return false; - } - } - return true; - } - - private computeHash(text: string): string { - return this.project.projectService.host.createHash(text); - } - - private getSourceFile(): SourceFile { - return this.project.getSourceFile(this.scriptInfo.path); - } - - /** - * @return {boolean} indicates if the shape signature has changed since last update. - */ - public updateShapeSignature() { - const sourceFile = this.getSourceFile(); - if (!sourceFile) { - return true; - } - - const lastSignature = this.lastCheckedShapeSignature; - if (sourceFile.isDeclarationFile) { - this.lastCheckedShapeSignature = this.computeHash(sourceFile.text); - } - else { - const emitOutput = this.project.getFileEmitOutput(this.scriptInfo, /*emitOnlyDtsFiles*/ true); - if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - this.lastCheckedShapeSignature = this.computeHash(emitOutput.outputFiles[0].text); - } - } - return !lastSignature || this.lastCheckedShapeSignature !== lastSignature; - } - } - - export interface Builder { - readonly project: Project; - getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; - onProjectUpdateGraph(): void; - emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean; - clear(): void; - } - - abstract class AbstractBuilder implements Builder { - - /** - * stores set of files from the project. - * NOTE: this field is created on demand and should not be accessed directly. - * Use 'getFileInfos' instead. - */ - private fileInfos_doNotAccessDirectly: Map; - - constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) { - } - - private getFileInfos() { - return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createMap()); - } - - protected hasFileInfos() { - return !!this.fileInfos_doNotAccessDirectly; - } - - public clear() { - // drop the existing list - it will be re-created as necessary - this.fileInfos_doNotAccessDirectly = undefined; - } - - protected getFileInfo(path: Path): T { - return this.getFileInfos().get(path); - } - - protected getOrCreateFileInfo(path: Path): T { - let fileInfo = this.getFileInfo(path); - if (!fileInfo) { - const scriptInfo = this.project.getScriptInfo(path); - fileInfo = new this.ctor(scriptInfo, this.project); - this.setFileInfo(path, fileInfo); - } - return fileInfo; - } - - protected getFileInfoPaths(): Path[] { - return arrayFrom(this.getFileInfos().keys() as Iterator); - } - - protected setFileInfo(path: Path, info: T) { - this.getFileInfos().set(path, info); - } - - protected removeFileInfo(path: Path) { - this.getFileInfos().delete(path); - } - - protected forEachFileInfo(action: (fileInfo: T) => any) { - this.getFileInfos().forEach(action); - } - - abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; - abstract onProjectUpdateGraph(): void; - protected abstract ensureFileInfoIfInProject(scriptInfo: ScriptInfo): void; - - /** - * @returns {boolean} whether the emit was conducted or not - */ - emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean { - this.ensureFileInfoIfInProject(scriptInfo); - const fileInfo = this.getFileInfo(scriptInfo.path); - if (!fileInfo) { - return false; - } - - const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); - if (!emitSkipped) { - const projectRootPath = this.project.getProjectRootPath(); - for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); - writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); - } - } - return !emitSkipped; - } - } - - class NonModuleBuilder extends AbstractBuilder { - - constructor(public readonly project: Project) { - super(project, BuilderFileInfo); - } - - protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) { - if (this.project.containsScriptInfo(scriptInfo)) { - this.getOrCreateFileInfo(scriptInfo.path); - } - } - - onProjectUpdateGraph() { - if (this.hasFileInfos()) { - this.forEachFileInfo(fileInfo => { - if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) { - // This file was deleted from this project - this.removeFileInfo(fileInfo.scriptInfo.path); - } - }); - } - } - - /** - * Note: didn't use path as parameter because the returned file names will be directly - * consumed by the API user, which will use it to interact with file systems. Path - * should only be used internally, because the case sensitivity is not trustable. - */ - getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - const info = this.getOrCreateFileInfo(scriptInfo.path); - const singleFileResult = scriptInfo.hasMixedContent || scriptInfo.isDynamic ? [] : [scriptInfo.fileName]; - if (info.updateShapeSignature()) { - const options = this.project.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (options && (options.out || options.outFile)) { - return singleFileResult; - } - return this.project.getAllEmittableFiles(); - } - return singleFileResult; - } - } - - class ModuleBuilderFileInfo extends BuilderFileInfo { - references = createSortedArray(); - readonly referencedBy = createSortedArray(); - scriptVersionForReferences: string; - - static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison { - return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName); - } - - addReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - } - - removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void { - removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos); - } - - removeFileReferences() { - for (const reference of this.references) { - reference.removeReferencedBy(this); - } - clear(this.references); - } - } - - class ModuleBuilder extends AbstractBuilder { - - constructor(public readonly project: Project) { - super(project, ModuleBuilderFileInfo); - } - - private projectVersionForDependencyGraph: string; - - public clear() { - this.projectVersionForDependencyGraph = undefined; - super.clear(); - } - - private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray { - if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return createSortedArray(); - } - - const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos); - } - - protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { - this.ensureProjectDependencyGraphUpToDate(); - } - - onProjectUpdateGraph() { - // Update the graph only if we have computed graph earlier - if (this.hasFileInfos()) { - this.ensureProjectDependencyGraphUpToDate(); - } - } - - private ensureProjectDependencyGraphUpToDate() { - if (!this.projectVersionForDependencyGraph || this.project.getProjectVersion() !== this.projectVersionForDependencyGraph) { - const currentScriptInfos = this.project.getScriptInfos(); - for (const scriptInfo of currentScriptInfos) { - const fileInfo = this.getOrCreateFileInfo(scriptInfo.path); - this.updateFileReferences(fileInfo); - } - this.forEachFileInfo(fileInfo => { - if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) { - // This file was deleted from this project - fileInfo.removeFileReferences(); - this.removeFileInfo(fileInfo.scriptInfo.path); - } - }); - this.projectVersionForDependencyGraph = this.project.getProjectVersion(); - } - } - - private updateFileReferences(fileInfo: ModuleBuilderFileInfo) { - // Only need to update if the content of the file changed. - if (fileInfo.scriptVersionForReferences === fileInfo.scriptInfo.getLatestVersion()) { - return; - } - - const newReferences = this.getReferencedFileInfos(fileInfo); - const oldReferences = fileInfo.references; - enumerateInsertsAndDeletes(newReferences, oldReferences, - /*inserted*/ newReference => newReference.addReferencedBy(fileInfo), - /*deleted*/ oldReference => { - // New reference is greater then current reference. That means - // the current reference doesn't exist anymore after parsing. So delete - // references. - oldReference.removeReferencedBy(fileInfo); - }, - /*compare*/ ModuleBuilderFileInfo.compareFileInfos); - - fileInfo.references = newReferences; - fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion(); - } - - getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - this.ensureProjectDependencyGraphUpToDate(); - - const singleFileResult = scriptInfo.hasMixedContent || scriptInfo.isDynamic ? [] : [scriptInfo.fileName]; - const fileInfo = this.getFileInfo(scriptInfo.path); - if (!fileInfo || !fileInfo.updateShapeSignature()) { - return singleFileResult; - } - - if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return this.project.getAllEmittableFiles(); - } - - const options = this.project.getCompilerOptions(); - if (options && (options.isolatedModules || options.out || options.outFile)) { - return singleFileResult; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - - // Use slice to clone the array to avoid manipulating in place - const queue = fileInfo.referencedBy.slice(0); - const fileNameSet = createMap(); - fileNameSet.set(scriptInfo.fileName, scriptInfo); - while (queue.length > 0) { - const processingFileInfo = queue.pop(); - if (processingFileInfo.updateShapeSignature() && processingFileInfo.referencedBy.length > 0) { - for (const potentialFileInfo of processingFileInfo.referencedBy) { - if (!fileNameSet.has(potentialFileInfo.scriptInfo.fileName)) { - queue.push(potentialFileInfo); - } - } - } - fileNameSet.set(processingFileInfo.scriptInfo.fileName, processingFileInfo.scriptInfo); - } - const result: string[] = []; - fileNameSet.forEach((scriptInfo, fileName) => { - if (shouldEmitFile(scriptInfo)) { - result.push(fileName); - } - }); - return result; - } - } - - export function createBuilder(project: Project): Builder { - const moduleKind = project.getCompilerOptions().module; - switch (moduleKind) { - case ModuleKind.None: - return new NonModuleBuilder(project); - default: - return new ModuleBuilder(project); - } - } -} diff --git a/src/server/project.ts b/src/server/project.ts index f4a83c94449..27f0744537a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -193,6 +193,7 @@ namespace ts.server { return result.module; } + /*@internal*/ constructor( /*@internal*/readonly projectName: string, readonly projectKind: ProjectKind, @@ -758,7 +759,7 @@ namespace ts.server { hasChanges = this.updateGraphWorker() || hasChanges; } if (this.builder) { - this.builder.onProgramUpdateGraph(this.program, this.hasInvalidatedResolution); + this.builder.updateProgram(this.program); } } else { @@ -1030,6 +1031,7 @@ namespace ts.server { super.setCompilerOptions(newOptions); } + /*@internal*/ constructor( projectService: ProjectService, documentRegistry: DocumentRegistry, @@ -1119,6 +1121,7 @@ namespace ts.server { private projectErrors: Diagnostic[]; + /*@internal*/ constructor(configFileName: NormalizedPath, projectService: ProjectService, documentRegistry: DocumentRegistry, @@ -1363,6 +1366,7 @@ namespace ts.server { export class ExternalProject extends Project { excludedFiles: ReadonlyArray = []; private typeAcquisition: TypeAcquisition; + /*@internal*/ constructor(public externalProjectName: string, projectService: ProjectService, documentRegistry: DocumentRegistry, From 3b4cbebee094e01dac4f8036df0f663dac6e3e17 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 29 Sep 2017 18:13:38 -0700 Subject: [PATCH 139/144] Handle repeated substitution of the same symbol during extraction Old: Insert the same Node for every occurrence. The position was repeatedly mutated during printing. New: Thunk the Node so that a new one is constructed for every occurrence. Fixes #18857 --- src/harness/unittests/extractConstants.ts | 11 ++++----- src/harness/unittests/extractFunctions.ts | 11 ++++----- src/services/refactors/extractSymbol.ts | 24 ++++++++++--------- .../extractConstant_RepeatedSubstitution.ts | 19 +++++++++++++++ .../extractFunction_RepeatedSubstitution.ts | 22 +++++++++++++++++ 5 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/extractConstant/extractConstant_RepeatedSubstitution.ts create mode 100644 tests/baselines/reference/extractFunction/extractFunction_RepeatedSubstitution.ts diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts index 179affd86c9..28109a6fa4d 100644 --- a/src/harness/unittests/extractConstants.ts +++ b/src/harness/unittests/extractConstants.ts @@ -84,12 +84,11 @@ namespace ts { let x = [#|t + 1|]; }`); -// TODO (18857): handle repeated substitution -// testExtractConstant("extractConstant_RepeatedSubstitution", -// `namespace X { -// export const j = 10; -// export const y = [#|j * j|]; -// }`); + testExtractConstant("extractConstant_RepeatedSubstitution", + `namespace X { + export const j = 10; + export const y = [#|j * j|]; +}`); testExtractConstant("extractConstant_VariableList_const", `const a = 1, b = [#|a + 1|];`); diff --git a/src/harness/unittests/extractFunctions.ts b/src/harness/unittests/extractFunctions.ts index 291324e08f3..750e590ca4c 100644 --- a/src/harness/unittests/extractFunctions.ts +++ b/src/harness/unittests/extractFunctions.ts @@ -355,12 +355,11 @@ function parsePrimaryExpression(): any { [#|function G() { }|] }`); -// TODO (18857): handle repeated substitution -// testExtractFunction("extractFunction_RepeatedSubstitution", -// `namespace X { -// export const j = 10; -// export const y = [#|j * j|]; -// }`); + testExtractFunction("extractFunction_RepeatedSubstitution", + `namespace X { + export const j = 10; + export const y = [#|j * j|]; +}`); }); function testExtractFunction(caption: string, text: string) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index df9da034da6..5ca6efbd57d 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1048,7 +1048,7 @@ namespace ts.refactor.extractSymbol { } } - function transformFunctionBody(body: Node, writes: ReadonlyArray, substitutions: ReadonlyMap, hasReturn: boolean): { body: Block, returnValueProperty: string } { + function transformFunctionBody(body: Node, writes: ReadonlyArray, substitutions: ReadonlyMap<() => Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } { if (isBlock(body) && !writes && substitutions.size === 0) { // already block, no writes to propagate back, no substitutions - can use node as is return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; @@ -1096,21 +1096,21 @@ namespace ts.refactor.extractSymbol { const oldIgnoreReturns = ignoreReturns; ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); const substitution = substitutions.get(getNodeId(node).toString()); - const result = substitution || visitEachChild(node, visitor, nullTransformationContext); + const result = substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext); ignoreReturns = oldIgnoreReturns; return result; } } } - function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap): Expression { + function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap<() => Node>): Expression { return substitutions.size ? visitor(initializer) as Expression : initializer; function visitor(node: Node): VisitResult { const substitution = substitutions.get(getNodeId(node).toString()); - return substitution || visitEachChild(node, visitor, nullTransformationContext); + return substitution ? substitution() : visitEachChild(node, visitor, nullTransformationContext); } } @@ -1239,7 +1239,7 @@ namespace ts.refactor.extractSymbol { interface ScopeUsages { readonly usages: Map; readonly typeParameterUsages: Map; // Key is type ID - readonly substitutions: Map; + readonly substitutions: Map<() => Node>; } interface ReadsAndWrites { @@ -1258,7 +1258,7 @@ namespace ts.refactor.extractSymbol { const allTypeParameterUsages = createMap(); // Key is type ID const usagesPerScope: ScopeUsages[] = []; - const substitutionsPerScope: Map[] = []; + const substitutionsPerScope: Map<() => Node>[] = []; const functionErrorsPerScope: Diagnostic[][] = []; const constantErrorsPerScope: Diagnostic[][] = []; const visibleDeclarationsInExtractedRange: Symbol[] = []; @@ -1270,8 +1270,8 @@ namespace ts.refactor.extractSymbol { // initialize results for (const scope of scopes) { - usagesPerScope.push({ usages: createMap(), typeParameterUsages: createMap(), substitutions: createMap() }); - substitutionsPerScope.push(createMap()); + usagesPerScope.push({ usages: createMap(), typeParameterUsages: createMap(), substitutions: createMap<() => Expression>() }); + substitutionsPerScope.push(createMap<() => Expression>()); functionErrorsPerScope.push( isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration @@ -1567,18 +1567,20 @@ namespace ts.refactor.extractSymbol { } } - function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName { + function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol, scopeDecl: Node, isTypeNode: boolean): () => (PropertyAccessExpression | EntityName) { if (!symbol) { return undefined; } if (symbol.getDeclarations().some(d => d.parent === scopeDecl)) { - return createIdentifier(symbol.name); + return () => createIdentifier(symbol.name); } const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode); if (prefix === undefined) { return undefined; } - return isTypeNode ? createQualifiedName(prefix, createIdentifier(symbol.name)) : createPropertyAccess(prefix, symbol.name); + return isTypeNode + ? () => createQualifiedName(prefix(), createIdentifier(symbol.name)) + : () => createPropertyAccess(prefix(), symbol.name); } } diff --git a/tests/baselines/reference/extractConstant/extractConstant_RepeatedSubstitution.ts b/tests/baselines/reference/extractConstant/extractConstant_RepeatedSubstitution.ts new file mode 100644 index 00000000000..da92e8be933 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_RepeatedSubstitution.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== +namespace X { + export const j = 10; + export const y = j * j; +} +// ==SCOPE::Extract to constant in enclosing scope== +namespace X { + export const j = 10; + const newLocal = j * j; + + export const y = /*RENAME*/newLocal; +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = X.j * X.j; + +namespace X { + export const j = 10; + export const y = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractFunction/extractFunction_RepeatedSubstitution.ts b/tests/baselines/reference/extractFunction/extractFunction_RepeatedSubstitution.ts new file mode 100644 index 00000000000..080f694360d --- /dev/null +++ b/tests/baselines/reference/extractFunction/extractFunction_RepeatedSubstitution.ts @@ -0,0 +1,22 @@ +// ==ORIGINAL== +namespace X { + export const j = 10; + export const y = j * j; +} +// ==SCOPE::Extract to function in namespace 'X'== +namespace X { + export const j = 10; + export const y = /*RENAME*/newFunction(); + + function newFunction() { + return j * j; + } +} +// ==SCOPE::Extract to function in global scope== +namespace X { + export const j = 10; + export const y = /*RENAME*/newFunction(); +} +function newFunction() { + return X.j * X.j; +} From d620129d4a50457146e390d791aa4484a6c64df6 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 2 Oct 2017 17:02:27 -0700 Subject: [PATCH 140/144] Ensure getResolvedSignature never returns `undefined` (#18883) --- src/compiler/checker.ts | 9 ++++----- src/compiler/types.ts | 4 ++-- src/services/symbolDisplay.ts | 4 ---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e3ba62bf3be..1c1ccd87d3c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16561,10 +16561,9 @@ namespace ts { * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; * the function will fill it up with appropriate candidate signatures */ - function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature { + function getResolvedJsxStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { Debug.assert(!(elementType.flags & TypeFlags.Union)); - const callSignature = resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray); - return callSignature; + return resolveStatelessJsxOpeningLikeElement(openingLikeElement, elementType, candidatesOutArray); } /** @@ -16576,7 +16575,7 @@ namespace ts { * @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions. * otherwise return undefined if tag-name of the opening-like element doesn't have call signatures */ - function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature { + function resolveStatelessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature | undefined { // If this function is called from language service, elementType can be a union type. This is not possible if the function is called from compiler (see: resolveCustomJsxElementAttributesType) if (elementType.flags & TypeFlags.Union) { const types = (elementType as UnionType).types; @@ -16609,7 +16608,7 @@ namespace ts { case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: // This code-path is called by language service - return resolveStatelessJsxOpeningLikeElement(node, checkExpression((node).tagName), candidatesOutArray); + return resolveStatelessJsxOpeningLikeElement(node, checkExpression((node).tagName), candidatesOutArray) || unknownSignature; } Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 01036862980..6d94b2b0d95 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2652,10 +2652,10 @@ namespace ts { getRootSymbols(symbol: Symbol): Symbol[]; getContextualType(node: Expression): Type | undefined; /** - * returns unknownSignature in the case of an error. Don't know when it returns undefined. + * returns unknownSignature in the case of an error. * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. */ - getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature | undefined; + getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature; getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined; isImplementationOfOverload(node: FunctionLike): boolean | undefined; isUndefinedSymbol(symbol: Symbol): boolean; diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index ad8e7ddf975..a399610d823 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -136,10 +136,6 @@ namespace ts.SymbolDisplay { if (callExpressionLike) { const candidateSignatures: Signature[] = []; signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures); - if (!signature && candidateSignatures.length) { - // Use the first candidate: - signature = candidateSignatures[0]; - } const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword); From 3a2c723a69df2d3b4aca2973db2155446e83ab32 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 2 Oct 2017 17:04:28 -0700 Subject: [PATCH 141/144] Allow parsing a qualified type name containing a reserved word (#18888) --- src/compiler/parser.ts | 8 +++--- ...perCallsInNonConstructorMembers.errors.txt | 28 +++++++++++-------- ...dClassSuperCallsInNonConstructorMembers.js | 4 +-- ...assSuperCallsInNonConstructorMembers.types | 12 ++++---- ...docParameterParsingInfiniteLoop.errors.txt | 4 +-- .../parseEntityNameWithReservedWord.js | 11 ++++++++ .../parseEntityNameWithReservedWord.symbols | 13 +++++++++ .../parseEntityNameWithReservedWord.types | 13 +++++++++ ...serErrorRecovery_ParameterList6.errors.txt | 14 +++------- .../parserErrorRecovery_ParameterList6.js | 3 +- .../parserErrorRecovery_ParameterList6.types | 5 ++-- .../parservoidInQualifiedName2.errors.txt | 10 ++----- .../reference/parservoidInQualifiedName2.js | 2 +- .../parservoidInQualifiedName2.types | 4 +-- .../parseEntityNameWithReservedWord.ts | 2 ++ 15 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 tests/baselines/reference/parseEntityNameWithReservedWord.js create mode 100644 tests/baselines/reference/parseEntityNameWithReservedWord.symbols create mode 100644 tests/baselines/reference/parseEntityNameWithReservedWord.types create mode 100644 tests/cases/compiler/parseEntityNameWithReservedWord.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index baa89c61335..112a4ecc2ee 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1217,8 +1217,8 @@ namespace ts { return createIdentifier(isIdentifier(), diagnosticMessage); } - function parseIdentifierName(): Identifier { - return createIdentifier(tokenIsIdentifierOrKeyword(token())); + function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { + return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); } function isLiteralPropertyName(): boolean { @@ -1956,7 +1956,7 @@ namespace ts { } function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { - let entity: EntityName = allowReservedWords ? parseIdentifierName() : parseIdentifier(diagnosticMessage); + let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); let dotPos = scanner.getStartPos(); while (parseOptional(SyntaxKind.DotToken)) { if (token() === SyntaxKind.LessThanToken) { @@ -2096,7 +2096,7 @@ namespace ts { function parseTypeReference(): TypeReferenceNode { const node = createNode(SyntaxKind.TypeReference); - node.typeName = parseEntityName(/*allowReservedWords*/ !!(contextFlags & NodeFlags.JSDoc), Diagnostics.Type_expected); + node.typeName = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); if (!scanner.hasPrecedingLineBreak() && token() === SyntaxKind.LessThanToken) { node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } diff --git a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.errors.txt b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.errors.txt index ab79f8d3e23..84d9c313995 100644 --- a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.errors.txt +++ b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.errors.txt @@ -1,16 +1,18 @@ -tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS1110: Type expected. -tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,8): error TS2304: Cannot find name 'super'. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,13): error TS1005: '=' expected. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(8,14): error TS1109: Expression expected. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(10,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(13,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(17,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. -tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS1110: Type expected. -tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,15): error TS2304: Cannot find name 'super'. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,20): error TS1005: '=' expected. +tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(20,21): error TS1109: Expression expected. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(22,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(25,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts(29,9): error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. -==== tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts (10 errors) ==== +==== tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassSuperCallsInNonConstructorMembers.ts (12 errors) ==== // error to use super calls outside a constructor class Base { @@ -20,9 +22,11 @@ tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassS class Derived extends Base { a: super(); ~~~~~ -!!! error TS1110: Type expected. - ~~~~~ -!!! error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. +!!! error TS2304: Cannot find name 'super'. + ~ +!!! error TS1005: '=' expected. + ~ +!!! error TS1109: Expression expected. b() { super(); ~~~~~ @@ -42,9 +46,11 @@ tests/cases/conformance/classes/constructorDeclarations/superCalls/derivedClassS static a: super(); ~~~~~ -!!! error TS1110: Type expected. - ~~~~~ -!!! error TS2337: Super calls are not permitted outside constructors or in nested functions inside constructors. +!!! error TS2304: Cannot find name 'super'. + ~ +!!! error TS1005: '=' expected. + ~ +!!! error TS1109: Expression expected. static b() { super(); ~~~~~ diff --git a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.js b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.js index 0205636e1f7..019e29f1940 100644 --- a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.js +++ b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.js @@ -52,7 +52,7 @@ var Derived = /** @class */ (function (_super) { __extends(Derived, _super); function Derived() { var _this = _super !== null && _super.apply(this, arguments) || this; - _this.a = _this = _super.call(this) || this; + _this.a = (); return _this; } Derived.prototype.b = function () { @@ -83,6 +83,6 @@ var Derived = /** @class */ (function (_super) { enumerable: true, configurable: true }); - Derived.a = _this = _super.call(this) || this; + Derived.a = (); return Derived; }(Base)); diff --git a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.types b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.types index 99d0c6e09f8..5964a25f6d4 100644 --- a/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.types +++ b/tests/baselines/reference/derivedClassSuperCallsInNonConstructorMembers.types @@ -14,9 +14,9 @@ class Derived extends Base { a: super(); >a : any -> : No type information available! ->super() : void ->super : any +>super : No type information available! +>() : any +> : any b() { >b : () => void @@ -46,9 +46,9 @@ class Derived extends Base { static a: super(); >a : any -> : No type information available! ->super() : void ->super : any +>super : No type information available! +>() : any +> : any static b() { >b : () => void diff --git a/tests/baselines/reference/jsdocParameterParsingInfiniteLoop.errors.txt b/tests/baselines/reference/jsdocParameterParsingInfiniteLoop.errors.txt index e42d67a62c1..a43c72cf0c7 100644 --- a/tests/baselines/reference/jsdocParameterParsingInfiniteLoop.errors.txt +++ b/tests/baselines/reference/jsdocParameterParsingInfiniteLoop.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/example.js(3,20): error TS1003: Identifier expected. +tests/cases/compiler/example.js(3,20): error TS1110: Type expected. ==== tests/cases/compiler/example.js (1 errors) ==== @@ -6,6 +6,6 @@ tests/cases/compiler/example.js(3,20): error TS1003: Identifier expected. /** * @type {function(@foo)} ~ -!!! error TS1003: Identifier expected. +!!! error TS1110: Type expected. */ let x; \ No newline at end of file diff --git a/tests/baselines/reference/parseEntityNameWithReservedWord.js b/tests/baselines/reference/parseEntityNameWithReservedWord.js new file mode 100644 index 00000000000..2de1b14b059 --- /dev/null +++ b/tests/baselines/reference/parseEntityNameWithReservedWord.js @@ -0,0 +1,11 @@ +//// [parseEntityNameWithReservedWord.ts] +enum Bool { false } +const x: Bool.false = Bool.false; + + +//// [parseEntityNameWithReservedWord.js] +var Bool; +(function (Bool) { + Bool[Bool["false"] = 0] = "false"; +})(Bool || (Bool = {})); +var x = Bool["false"]; diff --git a/tests/baselines/reference/parseEntityNameWithReservedWord.symbols b/tests/baselines/reference/parseEntityNameWithReservedWord.symbols new file mode 100644 index 00000000000..9c6599a9f0d --- /dev/null +++ b/tests/baselines/reference/parseEntityNameWithReservedWord.symbols @@ -0,0 +1,13 @@ +=== tests/cases/compiler/parseEntityNameWithReservedWord.ts === +enum Bool { false } +>Bool : Symbol(Bool, Decl(parseEntityNameWithReservedWord.ts, 0, 0)) +>false : Symbol(Bool.false, Decl(parseEntityNameWithReservedWord.ts, 0, 11)) + +const x: Bool.false = Bool.false; +>x : Symbol(x, Decl(parseEntityNameWithReservedWord.ts, 1, 5)) +>Bool : Symbol(Bool, Decl(parseEntityNameWithReservedWord.ts, 0, 0)) +>false : Symbol(Bool.false, Decl(parseEntityNameWithReservedWord.ts, 0, 11)) +>Bool.false : Symbol(Bool.false, Decl(parseEntityNameWithReservedWord.ts, 0, 11)) +>Bool : Symbol(Bool, Decl(parseEntityNameWithReservedWord.ts, 0, 0)) +>false : Symbol(Bool.false, Decl(parseEntityNameWithReservedWord.ts, 0, 11)) + diff --git a/tests/baselines/reference/parseEntityNameWithReservedWord.types b/tests/baselines/reference/parseEntityNameWithReservedWord.types new file mode 100644 index 00000000000..a7287ea4010 --- /dev/null +++ b/tests/baselines/reference/parseEntityNameWithReservedWord.types @@ -0,0 +1,13 @@ +=== tests/cases/compiler/parseEntityNameWithReservedWord.ts === +enum Bool { false } +>Bool : Bool +>false : Bool + +const x: Bool.false = Bool.false; +>x : Bool +>Bool : any +>false : Bool +>Bool.false : Bool +>Bool : typeof Bool +>false : Bool + diff --git a/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt b/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt index 61ad2fb175f..ef00e8afa86 100644 --- a/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt +++ b/tests/baselines/reference/parserErrorRecovery_ParameterList6.errors.txt @@ -1,15 +1,9 @@ -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,23): error TS1110: Type expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,28): error TS1003: Identifier expected. -tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(3,1): error TS1128: Declaration or statement expected. +tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts(2,23): error TS2304: Cannot find name 'break'. -==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/ErrorRecovery/ParameterLists/parserErrorRecovery_ParameterList6.ts (1 errors) ==== class Foo { public banana (x: break) { } ~~~~~ -!!! error TS1110: Type expected. - ~ -!!! error TS1003: Identifier expected. - } - ~ -!!! error TS1128: Declaration or statement expected. \ No newline at end of file +!!! error TS2304: Cannot find name 'break'. + } \ No newline at end of file diff --git a/tests/baselines/reference/parserErrorRecovery_ParameterList6.js b/tests/baselines/reference/parserErrorRecovery_ParameterList6.js index c5d8463f369..5a99b39431a 100644 --- a/tests/baselines/reference/parserErrorRecovery_ParameterList6.js +++ b/tests/baselines/reference/parserErrorRecovery_ParameterList6.js @@ -7,7 +7,6 @@ class Foo { var Foo = /** @class */ (function () { function Foo() { } + Foo.prototype.banana = function (x) { }; return Foo; }()); -break ; -{ } diff --git a/tests/baselines/reference/parserErrorRecovery_ParameterList6.types b/tests/baselines/reference/parserErrorRecovery_ParameterList6.types index 292f234b7b4..295b8f1d24d 100644 --- a/tests/baselines/reference/parserErrorRecovery_ParameterList6.types +++ b/tests/baselines/reference/parserErrorRecovery_ParameterList6.types @@ -3,8 +3,7 @@ class Foo { >Foo : Foo public banana (x: break) { } ->banana : (x: any) => any +>banana : (x: any) => void >x : any -> : No type information available! -> : any +>break : No type information available! } diff --git a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt index 7ca94a8989a..e3f7fce56ae 100644 --- a/tests/baselines/reference/parservoidInQualifiedName2.errors.txt +++ b/tests/baselines/reference/parservoidInQualifiedName2.errors.txt @@ -1,13 +1,7 @@ tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,9): error TS2503: Cannot find namespace 'x'. -tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,11): error TS1003: Identifier expected. -tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts(1,15): error TS1109: Expression expected. -==== tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts (3 errors) ==== +==== tests/cases/conformance/parser/ecmascript5/parservoidInQualifiedName2.ts (1 errors) ==== var v : x.void; ~ -!!! error TS2503: Cannot find namespace 'x'. - ~~~~ -!!! error TS1003: Identifier expected. - ~ -!!! error TS1109: Expression expected. \ No newline at end of file +!!! error TS2503: Cannot find namespace 'x'. \ No newline at end of file diff --git a/tests/baselines/reference/parservoidInQualifiedName2.js b/tests/baselines/reference/parservoidInQualifiedName2.js index e3ed00f3799..818f347cb18 100644 --- a/tests/baselines/reference/parservoidInQualifiedName2.js +++ b/tests/baselines/reference/parservoidInQualifiedName2.js @@ -2,4 +2,4 @@ var v : x.void; //// [parservoidInQualifiedName2.js] -var v = void ; +var v; diff --git a/tests/baselines/reference/parservoidInQualifiedName2.types b/tests/baselines/reference/parservoidInQualifiedName2.types index b3c5734bc3f..788b6c0204b 100644 --- a/tests/baselines/reference/parservoidInQualifiedName2.types +++ b/tests/baselines/reference/parservoidInQualifiedName2.types @@ -2,7 +2,5 @@ var v : x.void; >v : any >x : any -> : No type information available! ->void : undefined -> : any +>void : No type information available! diff --git a/tests/cases/compiler/parseEntityNameWithReservedWord.ts b/tests/cases/compiler/parseEntityNameWithReservedWord.ts new file mode 100644 index 00000000000..d05a078955c --- /dev/null +++ b/tests/cases/compiler/parseEntityNameWithReservedWord.ts @@ -0,0 +1,2 @@ +enum Bool { false } +const x: Bool.false = Bool.false; From a8b7f7d1e54d3693a28f14d6cd9b77c7a3c9d15a Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 2 Oct 2017 17:16:08 -0700 Subject: [PATCH 142/144] Include localized diagnostics (#18702) * Add lcl files * Add loclalization script * Add localization build targets * use async exists, and add assert * Generate lcg file * Add localize task to gulpFile * Only run loclaize if the generated files neededs update. Also run localize as part of local * Fix lint errors * Linter love * Respond to code review comments --- .gitignore | 12 +- .npmignore | 3 +- Gulpfile.ts | 44 +- Jakefile.js | 45 +- package.json | 2 + .../generateLocalizedDiagnosticMessages.ts | 139 + .../diagnosticMessages.generated.json.lcl | 8516 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8516 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8525 +++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8486 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8525 +++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8525 +++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8516 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8513 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8516 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8479 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8479 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8515 ++++++++++++++++ .../diagnosticMessages.generated.json.lcl | 8509 ++++++++++++++++ 19 files changed, 110848 insertions(+), 17 deletions(-) create mode 100644 scripts/generateLocalizedDiagnosticMessages.ts create mode 100644 src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl create mode 100644 src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl diff --git a/.gitignore b/.gitignore index 0b29eebf521..9b93436e7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,11 +19,6 @@ tests/baselines/local/projectOutput/* tests/baselines/reference/testresults.tap tests/services/baselines/prototyping/local/* tests/services/browser/typescriptServices.js -scripts/authors.js -scripts/configureNightly.js -scripts/processDiagnosticMessages.d.ts -scripts/processDiagnosticMessages.js -scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js src/harness/*.js src/compiler/diagnosticInformationMap.generated.ts src/compiler/diagnosticMessages.generated.json @@ -43,7 +38,12 @@ scripts/run.bat scripts/word2md.js scripts/buildProtocol.js scripts/ior.js -scripts/buildProtocol.js +scripts/authors.js +scripts/configureNightly.js +scripts/processDiagnosticMessages.d.ts +scripts/processDiagnosticMessages.js +scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js +scripts/generateLocalizedDiagnosticMessages.js scripts/*.js.map scripts/typings/ coverage/ diff --git a/.npmignore b/.npmignore index 50ae145423a..177e27f3845 100644 --- a/.npmignore +++ b/.npmignore @@ -5,6 +5,7 @@ internal issue_template.md jenkins.sh lib/README.md +lib/enu netci.groovy pull_request_template.md scripts @@ -16,5 +17,5 @@ Jakefile.js .gitattributes .settings/ .travis.yml -.vscode/ +.vscode/ test.config \ No newline at end of file diff --git a/Gulpfile.ts b/Gulpfile.ts index 873ea0991bd..c1c1be38a86 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -87,6 +87,7 @@ const harnessDirectory = "src/harness/"; const libraryDirectory = "src/lib/"; const scriptsDirectory = "scripts/"; const docDirectory = "doc/"; +const lclDirectory = "src/loc/lcl"; const builtDirectory = "built/"; const builtLocalDirectory = "built/local/"; @@ -367,6 +368,36 @@ gulp.task(builtGeneratedDiagnosticMessagesJSON, [diagnosticInfoMapTs], (done) => gulp.task("generate-diagnostics", "Generates a diagnostic file in TypeScript based on an input JSON file", [diagnosticInfoMapTs]); +// Localize diagnostics script +const generateLocalizedDiagnosticMessagesJs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.js"); +const generateLocalizedDiagnosticMessagesTs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.ts"); + +gulp.task(generateLocalizedDiagnosticMessagesJs, /*help*/ false, [], () => { + const settings: tsc.Settings = getCompilerSettings({ + target: "es5", + declaration: false, + removeComments: true, + noResolve: false, + stripInternal: false, + types: ["node", "xml2js"] + }, /*useBuiltCompiler*/ false); + return gulp.src(generateLocalizedDiagnosticMessagesTs) + .pipe(newer(generateLocalizedDiagnosticMessagesJs)) + .pipe(sourcemaps.init()) + .pipe(tsc(settings)) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(".")); +}); + +// Localize diagnostics +const generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg"); +gulp.task(generatedLCGFile, [generateLocalizedDiagnosticMessagesJs, diagnosticInfoMapTs], (done) => { + if (fs.existsSync(builtLocalDirectory) && needsUpdate(generatedDiagnosticMessagesJSON, generatedLCGFile)) { + exec(host, [generateLocalizedDiagnosticMessagesJs, lclDirectory, builtLocalDirectory, generatedDiagnosticMessagesJSON], done, done); + } +}); + +gulp.task("localize", [generatedLCGFile]); const servicesFile = path.join(builtLocalDirectory, "typescriptServices.js"); const standaloneDefinitionsFile = path.join(builtLocalDirectory, "typescriptServices.d.ts"); @@ -494,7 +525,7 @@ gulp.task(typesMapJson, /*help*/ false, [], () => { }); gulp.task("lssl", "Builds language service server library", [tsserverLibraryFile]); -gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile]); +gulp.task("local", "Builds the full compiler and services", [builtLocalCompiler, servicesFile, serverFile, builtGeneratedDiagnosticMessagesJSON, tsserverLibraryFile, "localize"]); gulp.task("tsc", "Builds only the compiler", [builtLocalCompiler]); // Generate Markdown spec @@ -536,15 +567,16 @@ gulp.task("dontUseDebugMode", /*help*/ false, [], (done) => { useDebugMode = fal gulp.task("VerifyLKG", /*help*/ false, [], () => { const expectedFiles = [builtLocalCompiler, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, typingsInstallerJs, cancellationTokenJs].concat(libraryTargets); - const missingFiles = expectedFiles.filter(function(f) { - return !fs.existsSync(f); - }); + const missingFiles = expectedFiles. + concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d, "diagnosticMessages.generated.json"); })). + concat(generatedLCGFile). + filter(f => !fs.existsSync(f)); if (missingFiles.length > 0) { throw new Error("Cannot replace the LKG unless all built targets are present in directory " + builtLocalDirectory + ". The following files are missing:\n" + missingFiles.join("\n")); } // Copy all the targets into the LKG directory - return gulp.src(expectedFiles).pipe(gulp.dest(LKGDirectory)); + return gulp.src([...expectedFiles, path.join(builtLocalDirectory, "**"), `!${path.join(builtLocalDirectory, "tslint")}`, `!${path.join(builtLocalDirectory, "*.*")}`]).pipe(gulp.dest(LKGDirectory)); }); gulp.task("LKGInternal", /*help*/ false, ["lib", "local"]); @@ -1051,7 +1083,7 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: const fileMatcher = cmdLineOptions["files"]; const files = fileMatcher ? `src/**/${fileMatcher}` - : "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; + : "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`; console.log("Linting: " + cmd); child_process.execSync(cmd, { stdio: [0, 1, 2] }); diff --git a/Jakefile.js b/Jakefile.js index 8a4c67ac84b..85ff458838b 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -17,6 +17,7 @@ var libraryDirectory = "src/lib/"; var scriptsDirectory = "scripts/"; var unittestsDirectory = "src/harness/unittests/"; var docDirectory = "doc/"; +var lclDirectory = "src/loc/lcl"; var builtDirectory = "built/"; var builtLocalDirectory = "built/local/"; @@ -427,7 +428,40 @@ compileFile(processDiagnosticMessagesJs, [processDiagnosticMessagesTs], [processDiagnosticMessagesTs], [], - /*useBuiltCompiler*/ false); + /*useBuiltCompiler*/ false); + +// Localize diagnostics script +var generateLocalizedDiagnosticMessagesJs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.js"); +var generateLocalizedDiagnosticMessagesTs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.ts"); + +file(generateLocalizedDiagnosticMessagesTs); + +compileFile(generateLocalizedDiagnosticMessagesJs, + [generateLocalizedDiagnosticMessagesTs], + [generateLocalizedDiagnosticMessagesTs], + [], + /*useBuiltCompiler*/ false, { noOutFile: true, types: ["node", "xml2js"] }); + +// Localize diagnostics +var generatedLCGFile = path.join(builtLocalDirectory, "enu", "diagnosticMessages.generated.json.lcg"); +file(generatedLCGFile, [generateLocalizedDiagnosticMessagesJs, diagnosticInfoMapTs, generatedDiagnosticMessagesJSON], function () { + var cmd = host + " " + generateLocalizedDiagnosticMessagesJs + " " + lclDirectory + " " + builtLocalDirectory + " " + generatedDiagnosticMessagesJSON; + console.log(cmd); + var ex = jake.createExec([cmd]); + // Add listeners for output and error + ex.addListener("stdout", function (output) { + process.stdout.write(output); + }); + ex.addListener("stderr", function (error) { + process.stderr.write(error); + }); + ex.addListener("cmdEnd", function () { + complete(); + }); + ex.run(); +}, { async: true }); + +task("localize", [generatedLCGFile]); var buildProtocolTs = path.join(scriptsDirectory, "buildProtocol.ts"); var buildProtocolJs = path.join(scriptsDirectory, "buildProtocol.js"); @@ -644,7 +678,7 @@ task("build-fold-end", [], function () { // Local target to build the compiler and services desc("Builds the full compiler and services"); -task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, buildProtocolDts, builtGeneratedDiagnosticMessagesJSON, "lssl", "build-fold-end"]); +task("local", ["build-fold-start", "generate-diagnostics", "lib", tscFile, servicesFile, nodeDefinitionsFile, serverFile, buildProtocolDts, builtGeneratedDiagnosticMessagesJSON, "lssl", "localize", "build-fold-end"]); // Local target to build only tsc.js desc("Builds only the compiler"); @@ -699,7 +733,10 @@ task("generate-spec", [specMd]); // Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory desc("Makes a new LKG out of the built js files"); task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () { - var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts, watchGuardFile].concat(libraryTargets); + var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts, watchGuardFile]. + concat(libraryTargets). + concat(fs.readdirSync(lclDirectory).map(function (d) { return path.join(builtLocalDirectory, d) })). + concat(path.dirname(generatedLCGFile)); var missingFiles = expectedFiles.filter(function (f) { return !fs.existsSync(f); }); @@ -1229,7 +1266,7 @@ task("lint", ["build-rules"], () => { const fileMatcher = process.env.f || process.env.file || process.env.files; const files = fileMatcher ? `src/**/${fileMatcher}` - : "Gulpfile.ts 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; + : "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'"; const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`; console.log("Linting: " + cmd); jake.exec([cmd], { interactive: true }, () => { diff --git a/package.json b/package.json index ca7d48c7774..fe69dc8dd69 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "@types/q": "latest", "@types/run-sequence": "latest", "@types/through2": "latest", + "@types/xml2js": "^0.4.0", + "xml2js": "^0.4.19", "browser-resolve": "^1.11.2", "browserify": "latest", "chai": "latest", diff --git a/scripts/generateLocalizedDiagnosticMessages.ts b/scripts/generateLocalizedDiagnosticMessages.ts new file mode 100644 index 00000000000..edfe9575155 --- /dev/null +++ b/scripts/generateLocalizedDiagnosticMessages.ts @@ -0,0 +1,139 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as xml2js from "xml2js"; + +function main(): void { + const args = process.argv.slice(2); + if (args.length !== 3) { + console.log("Usage:"); + console.log("\tnode generateLocalizedDiagnosticMessages.js "); + return; + } + + const inputPath = args[0]; + const outputPath = args[1]; + const diagnosticsMapFilePath = args[2]; + + // generate the lcg file for enu + generateLCGFile(); + + // generate other langs + fs.readdir(inputPath, (err, files) => { + handleError(err); + files.forEach(visitDirectory); + }); + + return; + + function visitDirectory(name: string) { + const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl"); + const outputFilePath = path.join(outputPath, name, "diagnosticMessages.generated.json"); + fs.readFile(inputFilePath, (err, data) => { + handleError(err); + xml2js.parseString(data.toString(), (err, result) => { + handleError(err); + writeFile(outputFilePath, xmlObjectToString(result)); + }); + }); + } + + function handleError(err: null | object) { + if (err) { + console.error(err); + process.exit(1); + } + } + + function xmlObjectToString(o: any) { + const out: any = {}; + for (const item of o["LCX"]["Item"][0]["Item"][0]["Item"]) { + let ItemId = item["$"]["ItemId"]; + let Val = item["Str"][0]["Tgt"] ? item["Str"][0]["Tgt"][0]["Val"][0] : item["Str"][0]["Val"][0]; + + if (typeof ItemId !== "string" || typeof Val !== "string") { + console.error("Unexpected XML file structure"); + process.exit(1); + } + + if (ItemId.charAt(0) === ";") { + ItemId = ItemId.slice(1); // remove leading semicolon + } + + Val = Val.replace(/]5D;/, "]"); // unescape `]` + out[ItemId] = Val; + } + return JSON.stringify(out, undefined, 2); + } + + + function ensureDirectoryExists(directoryPath: string, action: () => void) { + fs.exists(directoryPath, exists => { + if (!exists) { + const basePath = path.dirname(directoryPath); + if (basePath !== directoryPath) { + return ensureDirectoryExists(basePath, () => fs.mkdir(directoryPath, action)); + } + } + action(); + }); + } + + function writeFile(fileName: string, contents: string) { + ensureDirectoryExists(path.dirname(fileName), () => { + fs.writeFile(fileName, contents, handleError); + }); + } + + function objectToList(o: Record) { + const list: { key: string, value: string }[] = []; + for (const key in o) { + list.push({ key, value: o[key] }); + } + return list; + } + + function generateLCGFile() { + return fs.readFile(diagnosticsMapFilePath, (err, data) => { + handleError(err); + writeFile( + path.join(outputPath, "enu", "diagnosticMessages.generated.json.lcg"), + getLCGFileXML( + objectToList(JSON.parse(data.toString())) + .sort((a, b) => a.key > b.key ? 1 : -1) // lcg sorted by property keys + .reduce((s, { key, value }) => s + getItemXML(key, value), "") + )); + }); + + function getItemXML(key: string, value: string) { + // escape entrt value + value = value.replace(/]/, "]5D;"); + + return ` + + + + + + `; + } + + function getLCGFileXML(items: string) { + return ` + + + + + + + + + + ${items} + + +`; + } + } +} + +main(); diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..ccfe22a59ff --- /dev/null +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + 或 <语言>-<区域> 形式。例如“{0}”或“{1}”。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + 类型。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()"。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..bed8ce927b8 --- /dev/null +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + 或 <語言>-<國家/地區>。例如 '{0}' 或 '{1}'。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + 類型。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()'。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..6a727b8b8e8 --- /dev/null +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8525 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + nebo . Třeba {0} nebo {1}.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + .]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ().]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..79859d9bae2 --- /dev/null +++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8486 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + oder - erforderlich, z. B. "{0}" oder "{1}".]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + " sein.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()".]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..bbf200457e9 --- /dev/null +++ b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8525 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + o -. Por ejemplo, '{0}' o '{1}'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + global.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()" en su lugar.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..9e8be1a2bfa --- /dev/null +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8525 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + ou -. Par exemple, '{0}' ou '{1}'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + global.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()' à la place.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..6bb0ed245b9 --- /dev/null +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + o -, ad esempio, '{0}' o '{1}'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + .]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..a7248df81af --- /dev/null +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8513 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + または - の形式で指定する必要があります (例: '{0}'、'{1}')。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + 型である必要があります。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()' を使用してください。]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..0db33b6bbe9 --- /dev/null +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + 또는 - 형식이어야 합니다. 예를 들어 '{0}' 또는 '{1}'입니다.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + 형식이어야 합니다.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()'를 사용하세요.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..c75652ece47 --- /dev/null +++ b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + lub -. Na przykład „{0}” lub „{1}”.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + .]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()”.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..a895cdec898 --- /dev/null +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + ou -. Por exemplo '{0}' ou '{1}'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + Promessa global.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()' em vez disso.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..780f933d28b --- /dev/null +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + или <язык>–<территория>. Например, "{0}" или "{1}".]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + .]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()".]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl new file mode 100644 index 00000000000..affa7a48347 --- /dev/null +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -0,0 +1,8509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or -. For example '{0}' or '{1}'.]]> + + veya - biçiminde olmalıdır. Örneğin, '{0}' veya '{1}'.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type.]]> + + türü olmalıdır.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ()' instead.]]> + + ()' kullanın.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 8ac01d76f531661229e183a3490797f2a09ad4bb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 2 Oct 2017 17:16:49 -0700 Subject: [PATCH 143/144] Separate namespace declarations in builder of intenal and exported interfaces and functions --- src/compiler/builder.ts | 55 +++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index baa88e0d20f..38969b85bac 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -18,7 +18,34 @@ namespace ts { text: string; } - /* @internal */ + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed { + const outputFiles: OutputFile[] = []; + let emittedSourceFiles: SourceFile[]; + const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + if (!isDetailed) { + return { outputFiles, emitSkipped: emitResult.emitSkipped }; + } + + return { + outputFiles, + emitSkipped: emitResult.emitSkipped, + diagnostics: emitResult.diagnostics, + sourceMaps: emitResult.sourceMaps, + emittedSourceFiles + }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + if (isDetailed) { + emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles); + } + } + } +} + +/* @internal */ +namespace ts { export interface Builder { /** * Call this to feed new program @@ -60,38 +87,12 @@ namespace ts { getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[]; } - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed { - const outputFiles: OutputFile[] = []; - let emittedSourceFiles: SourceFile[]; - const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - if (!isDetailed) { - return { outputFiles, emitSkipped: emitResult.emitSkipped }; - } - - return { - outputFiles, - emitSkipped: emitResult.emitSkipped, - diagnostics: emitResult.diagnostics, - sourceMaps: emitResult.sourceMaps, - emittedSourceFiles - }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, _onError: (message: string) => void, sourceFiles: SourceFile[]) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - if (isDetailed) { - emittedSourceFiles = addRange(emittedSourceFiles, sourceFiles); - } - } - } - interface FileInfo { fileName: string; version: string; signature: string; } - /* @internal */ export function createBuilder( getCanonicalFileName: (fileName: string) => string, getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed, From 7f7d0c6c7b9b27c46d2d24c9bcf698bc81dc06d5 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 2 Oct 2017 17:37:40 -0700 Subject: [PATCH 144/144] Baseline public API (#18897) * Add unit test which tracks public API changes * Accept strict function types updates * 100% Linefeeds, no carriage returns * How were these missing? * That would be why they were there * Extract and comment * Accept comment changes --- CopyrightNotice.txt | 30 +- Gulpfile.ts | 18 +- Jakefile.js | 13 +- src/harness/tsconfig.json | 3 +- src/harness/unittests/publicApi.ts | 10 + src/tsconfig-base.json | 1 + .../reference/api/tsserverlibrary.d.ts | 7587 +++++++++++++++++ tests/baselines/reference/api/typescript.d.ts | 4588 ++++++++++ 8 files changed, 12222 insertions(+), 28 deletions(-) create mode 100644 src/harness/unittests/publicApi.ts create mode 100644 tests/baselines/reference/api/tsserverlibrary.d.ts create mode 100644 tests/baselines/reference/api/typescript.d.ts diff --git a/CopyrightNotice.txt b/CopyrightNotice.txt index 0f6db1f75f7..884a031325b 100644 --- a/CopyrightNotice.txt +++ b/CopyrightNotice.txt @@ -1,15 +1,15 @@ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ - +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + diff --git a/Gulpfile.ts b/Gulpfile.ts index c1c1be38a86..1b095f829d3 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -34,6 +34,14 @@ const gulp = helpMaker(originalGulp); Error.stackTraceLimit = 1000; +/** + * This regexp exists to capture our const enums and replace them with normal enums in our public API + * - this is fine since we compile with preserveConstEnums, and ensures our consumers are not locked + * to the TS version they compile with. + */ +const constEnumCaptureRegexp = /^(\s*)(export )?const enum (\S+) {(\s*)$/gm; +const constEnumReplacement = "$1$2enum $3 {$4"; + const cmdLineOptions = minimist(process.argv.slice(2), { boolean: ["debug", "inspect", "light", "colors", "lint", "soft"], string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"], @@ -261,8 +269,8 @@ function getCompilerSettings(base: tsc.Settings, useBuiltCompiler?: boolean): ts } if (!useDebugMode) { if (copy.removeComments === undefined) copy.removeComments = true; - copy.newLine = "lf"; } + copy.newLine = "lf"; if (useBuiltCompiler === true) { copy.typescript = require("./built/local/typescript.js"); } @@ -432,7 +440,7 @@ gulp.task(servicesFile, /*help*/ false, ["lib", "generate-diagnostics"], () => { const completedDts = dts.pipe(prependCopyright(/*outputCopyright*/ true)) .pipe(insert.transform((contents, file) => { file.path = standaloneDefinitionsFile; - return contents.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"); + return contents.replace(constEnumCaptureRegexp, constEnumReplacement); })); return merge2([ completedJs, @@ -442,7 +450,7 @@ gulp.task(servicesFile, /*help*/ false, ["lib", "generate-diagnostics"], () => { completedDts.pipe(clone()) .pipe(insert.transform((content, file) => { file.path = nodeDefinitionsFile; - return content + "\r\nexport = ts;"; + return content + "\nexport = ts;"; })) .pipe(gulp.dest("src/services")), completedDts.pipe(clone()) @@ -509,7 +517,7 @@ gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile, typesMapJson], (do .pipe(gulp.dest("src/server")), dts.pipe(prependCopyright(/*outputCopyright*/ true)) .pipe(insert.transform((content) => { - return content + "\r\nexport = ts;\r\nexport as namespace ts;"; + return content.replace(constEnumCaptureRegexp, constEnumReplacement) + "\nexport = ts;\nexport as namespace ts;"; })) .pipe(gulp.dest("src/server")) ]); @@ -588,7 +596,7 @@ gulp.task("LKG", "Makes a new LKG out of the built js files", ["clean", "dontUse // Task to build the tests infrastructure using the built compiler const run = path.join(builtLocalDirectory, "run.js"); -gulp.task(run, /*help*/ false, [servicesFile], () => { +gulp.task(run, /*help*/ false, [servicesFile, tsserverLibraryFile], () => { const testProject = tsc.createProject("src/harness/tsconfig.json", getCompilerSettings({}, /*useBuiltCompiler*/ true)); return testProject.src() .pipe(newer(run)) diff --git a/Jakefile.js b/Jakefile.js index 85ff458838b..aed1a59c8b1 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -151,6 +151,7 @@ var harnessSources = harnessCoreSources.concat([ "programMissingFiles.ts", "symbolWalker.ts", "languageService.ts", + "publicApi.ts", ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ @@ -341,9 +342,7 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts options += " -sourcemap"; } } - else { - options += " --newLine LF"; - } + options += " --newLine LF"; if (opts.stripInternal) { options += " --stripInternal"; @@ -608,7 +607,7 @@ compileFile(servicesFile, servicesSources, [builtLocalDirectory, copyright].conc // Official node package definition file, pointed to by 'typings' in package.json // Created by appending 'export = ts;' at the end of the standalone file to turn it into an external module - var nodeDefinitionsFileContents = definitionFileContents + "\r\nexport = ts;"; + var nodeDefinitionsFileContents = definitionFileContents + "\nexport = ts;"; fs.writeFileSync(nodeDefinitionsFile, nodeDefinitionsFileContents); // Node package definition file to be distributed without the package. Created by replacing @@ -655,8 +654,8 @@ compileFile( // Appending exports at the end of the server library var tsserverLibraryDefinitionFileContents = fs.readFileSync(tsserverLibraryDefinitionFile).toString() + - "\r\nexport = ts;" + - "\r\nexport as namespace ts;"; + "\nexport = ts;" + + "\nexport as namespace ts;"; tsserverLibraryDefinitionFileContents = removeConstModifierFromEnumDeclarations(tsserverLibraryDefinitionFileContents); fs.writeFileSync(tsserverLibraryDefinitionFile, tsserverLibraryDefinitionFileContents); @@ -763,7 +762,7 @@ var run = path.join(builtLocalDirectory, "run.js"); compileFile( /*outFile*/ run, /*source*/ harnessSources, - /*prereqs*/[builtLocalDirectory, tscFile].concat(libraryTargets).concat(servicesSources).concat(harnessSources), + /*prereqs*/[builtLocalDirectory, tscFile, tsserverLibraryFile].concat(libraryTargets).concat(servicesSources).concat(harnessSources), /*prefixes*/[], /*useBuiltCompiler:*/ true, /*opts*/ { types: ["node", "mocha", "chai"], lib: "es6" }); diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 88999b2d979..bb7e0f7a297 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -135,6 +135,7 @@ "./unittests/textChanges.ts", "./unittests/telemetry.ts", "./unittests/languageService.ts", - "./unittests/programMissingFiles.ts" + "./unittests/programMissingFiles.ts", + "./unittests/publicApi.ts" ] } diff --git a/src/harness/unittests/publicApi.ts b/src/harness/unittests/publicApi.ts new file mode 100644 index 00000000000..bb37dbe9d8d --- /dev/null +++ b/src/harness/unittests/publicApi.ts @@ -0,0 +1,10 @@ +/// + +describe("Public APIs", () => { + it("for the language service and compiler should be acknowledged when they change", () => { + Harness.Baseline.runBaseline("api/typescript.d.ts", () => Harness.IO.readFile("built/local/typescript.d.ts")); + }); + it("for the language server should be acknowledged when they change", () => { + Harness.Baseline.runBaseline("api/tsserverlibrary.d.ts", () => Harness.IO.readFile("built/local/tsserverlibrary.d.ts")); + }); +}); \ No newline at end of file diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index e4b99632907..81905b50102 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -12,6 +12,7 @@ "stripInternal": true, "sourceMap": true, "target": "es5", + "newLine": "lf", "types": [] } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts new file mode 100644 index 00000000000..96303f6b109 --- /dev/null +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -0,0 +1,7587 @@ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + +declare namespace ts { + /** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ + interface MapLike { + [index: string]: T; + } + /** ES6 Map interface, only read methods included. */ + interface ReadonlyMap { + get(key: string): T | undefined; + has(key: string): boolean; + forEach(action: (value: T, key: string) => void): void; + readonly size: number; + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[string, T]>; + } + /** ES6 Map interface. */ + interface Map extends ReadonlyMap { + set(key: string, value: T): this; + delete(key: string): boolean; + clear(): void; + } + /** ES6 Iterator type. */ + interface Iterator { + next(): { + value: T; + done: false; + } | { + value: never; + done: true; + }; + } + /** Array that is only intended to be pushed to, never read. */ + interface Push { + push(...values: T[]): void; + } + type Path = string & { + __pathBrand: any; + }; + interface TextRange { + pos: number; + end: number; + } + enum SyntaxKind { + Unknown = 0, + EndOfFileToken = 1, + SingleLineCommentTrivia = 2, + MultiLineCommentTrivia = 3, + NewLineTrivia = 4, + WhitespaceTrivia = 5, + ShebangTrivia = 6, + ConflictMarkerTrivia = 7, + NumericLiteral = 8, + StringLiteral = 9, + JsxText = 10, + JsxTextAllWhiteSpaces = 11, + RegularExpressionLiteral = 12, + NoSubstitutionTemplateLiteral = 13, + TemplateHead = 14, + TemplateMiddle = 15, + TemplateTail = 16, + OpenBraceToken = 17, + CloseBraceToken = 18, + OpenParenToken = 19, + CloseParenToken = 20, + OpenBracketToken = 21, + CloseBracketToken = 22, + DotToken = 23, + DotDotDotToken = 24, + SemicolonToken = 25, + CommaToken = 26, + LessThanToken = 27, + LessThanSlashToken = 28, + GreaterThanToken = 29, + LessThanEqualsToken = 30, + GreaterThanEqualsToken = 31, + EqualsEqualsToken = 32, + ExclamationEqualsToken = 33, + EqualsEqualsEqualsToken = 34, + ExclamationEqualsEqualsToken = 35, + EqualsGreaterThanToken = 36, + PlusToken = 37, + MinusToken = 38, + AsteriskToken = 39, + AsteriskAsteriskToken = 40, + SlashToken = 41, + PercentToken = 42, + PlusPlusToken = 43, + MinusMinusToken = 44, + LessThanLessThanToken = 45, + GreaterThanGreaterThanToken = 46, + GreaterThanGreaterThanGreaterThanToken = 47, + AmpersandToken = 48, + BarToken = 49, + CaretToken = 50, + ExclamationToken = 51, + TildeToken = 52, + AmpersandAmpersandToken = 53, + BarBarToken = 54, + QuestionToken = 55, + ColonToken = 56, + AtToken = 57, + EqualsToken = 58, + PlusEqualsToken = 59, + MinusEqualsToken = 60, + AsteriskEqualsToken = 61, + AsteriskAsteriskEqualsToken = 62, + SlashEqualsToken = 63, + PercentEqualsToken = 64, + LessThanLessThanEqualsToken = 65, + GreaterThanGreaterThanEqualsToken = 66, + GreaterThanGreaterThanGreaterThanEqualsToken = 67, + AmpersandEqualsToken = 68, + BarEqualsToken = 69, + CaretEqualsToken = 70, + Identifier = 71, + BreakKeyword = 72, + CaseKeyword = 73, + CatchKeyword = 74, + ClassKeyword = 75, + ConstKeyword = 76, + ContinueKeyword = 77, + DebuggerKeyword = 78, + DefaultKeyword = 79, + DeleteKeyword = 80, + DoKeyword = 81, + ElseKeyword = 82, + EnumKeyword = 83, + ExportKeyword = 84, + ExtendsKeyword = 85, + FalseKeyword = 86, + FinallyKeyword = 87, + ForKeyword = 88, + FunctionKeyword = 89, + IfKeyword = 90, + ImportKeyword = 91, + InKeyword = 92, + InstanceOfKeyword = 93, + NewKeyword = 94, + NullKeyword = 95, + ReturnKeyword = 96, + SuperKeyword = 97, + SwitchKeyword = 98, + ThisKeyword = 99, + ThrowKeyword = 100, + TrueKeyword = 101, + TryKeyword = 102, + TypeOfKeyword = 103, + VarKeyword = 104, + VoidKeyword = 105, + WhileKeyword = 106, + WithKeyword = 107, + ImplementsKeyword = 108, + InterfaceKeyword = 109, + LetKeyword = 110, + PackageKeyword = 111, + PrivateKeyword = 112, + ProtectedKeyword = 113, + PublicKeyword = 114, + StaticKeyword = 115, + YieldKeyword = 116, + AbstractKeyword = 117, + AsKeyword = 118, + AnyKeyword = 119, + AsyncKeyword = 120, + AwaitKeyword = 121, + BooleanKeyword = 122, + ConstructorKeyword = 123, + DeclareKeyword = 124, + GetKeyword = 125, + IsKeyword = 126, + KeyOfKeyword = 127, + ModuleKeyword = 128, + NamespaceKeyword = 129, + NeverKeyword = 130, + ReadonlyKeyword = 131, + RequireKeyword = 132, + NumberKeyword = 133, + ObjectKeyword = 134, + SetKeyword = 135, + StringKeyword = 136, + SymbolKeyword = 137, + TypeKeyword = 138, + UndefinedKeyword = 139, + FromKeyword = 140, + GlobalKeyword = 141, + OfKeyword = 142, + QualifiedName = 143, + ComputedPropertyName = 144, + TypeParameter = 145, + Parameter = 146, + Decorator = 147, + PropertySignature = 148, + PropertyDeclaration = 149, + MethodSignature = 150, + MethodDeclaration = 151, + Constructor = 152, + GetAccessor = 153, + SetAccessor = 154, + CallSignature = 155, + ConstructSignature = 156, + IndexSignature = 157, + TypePredicate = 158, + TypeReference = 159, + FunctionType = 160, + ConstructorType = 161, + TypeQuery = 162, + TypeLiteral = 163, + ArrayType = 164, + TupleType = 165, + UnionType = 166, + IntersectionType = 167, + ParenthesizedType = 168, + ThisType = 169, + TypeOperator = 170, + IndexedAccessType = 171, + MappedType = 172, + LiteralType = 173, + ObjectBindingPattern = 174, + ArrayBindingPattern = 175, + BindingElement = 176, + ArrayLiteralExpression = 177, + ObjectLiteralExpression = 178, + PropertyAccessExpression = 179, + ElementAccessExpression = 180, + CallExpression = 181, + NewExpression = 182, + TaggedTemplateExpression = 183, + TypeAssertionExpression = 184, + ParenthesizedExpression = 185, + FunctionExpression = 186, + ArrowFunction = 187, + DeleteExpression = 188, + TypeOfExpression = 189, + VoidExpression = 190, + AwaitExpression = 191, + PrefixUnaryExpression = 192, + PostfixUnaryExpression = 193, + BinaryExpression = 194, + ConditionalExpression = 195, + TemplateExpression = 196, + YieldExpression = 197, + SpreadElement = 198, + ClassExpression = 199, + OmittedExpression = 200, + ExpressionWithTypeArguments = 201, + AsExpression = 202, + NonNullExpression = 203, + MetaProperty = 204, + TemplateSpan = 205, + SemicolonClassElement = 206, + Block = 207, + VariableStatement = 208, + EmptyStatement = 209, + ExpressionStatement = 210, + IfStatement = 211, + DoStatement = 212, + WhileStatement = 213, + ForStatement = 214, + ForInStatement = 215, + ForOfStatement = 216, + ContinueStatement = 217, + BreakStatement = 218, + ReturnStatement = 219, + WithStatement = 220, + SwitchStatement = 221, + LabeledStatement = 222, + ThrowStatement = 223, + TryStatement = 224, + DebuggerStatement = 225, + VariableDeclaration = 226, + VariableDeclarationList = 227, + FunctionDeclaration = 228, + ClassDeclaration = 229, + InterfaceDeclaration = 230, + TypeAliasDeclaration = 231, + EnumDeclaration = 232, + ModuleDeclaration = 233, + ModuleBlock = 234, + CaseBlock = 235, + NamespaceExportDeclaration = 236, + ImportEqualsDeclaration = 237, + ImportDeclaration = 238, + ImportClause = 239, + NamespaceImport = 240, + NamedImports = 241, + ImportSpecifier = 242, + ExportAssignment = 243, + ExportDeclaration = 244, + NamedExports = 245, + ExportSpecifier = 246, + MissingDeclaration = 247, + ExternalModuleReference = 248, + JsxElement = 249, + JsxSelfClosingElement = 250, + JsxOpeningElement = 251, + JsxClosingElement = 252, + JsxAttribute = 253, + JsxAttributes = 254, + JsxSpreadAttribute = 255, + JsxExpression = 256, + CaseClause = 257, + DefaultClause = 258, + HeritageClause = 259, + CatchClause = 260, + PropertyAssignment = 261, + ShorthandPropertyAssignment = 262, + SpreadAssignment = 263, + EnumMember = 264, + SourceFile = 265, + Bundle = 266, + JSDocTypeExpression = 267, + JSDocAllType = 268, + JSDocUnknownType = 269, + JSDocNullableType = 270, + JSDocNonNullableType = 271, + JSDocOptionalType = 272, + JSDocFunctionType = 273, + JSDocVariadicType = 274, + JSDocComment = 275, + JSDocTag = 276, + JSDocAugmentsTag = 277, + JSDocClassTag = 278, + JSDocParameterTag = 279, + JSDocReturnTag = 280, + JSDocTypeTag = 281, + JSDocTemplateTag = 282, + JSDocTypedefTag = 283, + JSDocPropertyTag = 284, + JSDocTypeLiteral = 285, + SyntaxList = 286, + NotEmittedStatement = 287, + PartiallyEmittedExpression = 288, + CommaListExpression = 289, + MergeDeclarationMarker = 290, + EndOfDeclarationMarker = 291, + Count = 292, + FirstAssignment = 58, + LastAssignment = 70, + FirstCompoundAssignment = 59, + LastCompoundAssignment = 70, + FirstReservedWord = 72, + LastReservedWord = 107, + FirstKeyword = 72, + LastKeyword = 142, + FirstFutureReservedWord = 108, + LastFutureReservedWord = 116, + FirstTypeNode = 158, + LastTypeNode = 173, + FirstPunctuation = 17, + LastPunctuation = 70, + FirstToken = 0, + LastToken = 142, + FirstTriviaToken = 2, + LastTriviaToken = 7, + FirstLiteralToken = 8, + LastLiteralToken = 13, + FirstTemplateToken = 13, + LastTemplateToken = 16, + FirstBinaryOperator = 27, + LastBinaryOperator = 70, + FirstNode = 143, + FirstJSDocNode = 267, + LastJSDocNode = 285, + FirstJSDocTagNode = 276, + LastJSDocTagNode = 285, + } + enum NodeFlags { + None = 0, + Let = 1, + Const = 2, + NestedNamespace = 4, + Synthesized = 8, + Namespace = 16, + ExportContext = 32, + ContainsThis = 64, + HasImplicitReturn = 128, + HasExplicitReturn = 256, + GlobalAugmentation = 512, + HasAsyncFunctions = 1024, + DisallowInContext = 2048, + YieldContext = 4096, + DecoratorContext = 8192, + AwaitContext = 16384, + ThisNodeHasError = 32768, + JavaScriptFile = 65536, + ThisNodeOrAnySubNodesHasError = 131072, + HasAggregatedChildData = 262144, + JSDoc = 1048576, + BlockScoped = 3, + ReachabilityCheckFlags = 384, + ReachabilityAndEmitFlags = 1408, + ContextFlags = 96256, + TypeExcludesFlags = 20480, + } + enum ModifierFlags { + None = 0, + Export = 1, + Ambient = 2, + Public = 4, + Private = 8, + Protected = 16, + Static = 32, + Readonly = 64, + Abstract = 128, + Async = 256, + Default = 512, + Const = 2048, + HasComputedFlags = 536870912, + AccessibilityModifier = 28, + ParameterPropertyModifier = 92, + NonPublicAccessibilityModifier = 24, + TypeScriptModifier = 2270, + ExportDefault = 513, + } + enum JsxFlags { + None = 0, + /** An element from a named property of the JSX.IntrinsicElements interface */ + IntrinsicNamedElement = 1, + /** An element inferred from the string index signature of the JSX.IntrinsicElements interface */ + IntrinsicIndexedElement = 2, + IntrinsicElement = 3, + } + interface Node extends TextRange { + kind: SyntaxKind; + flags: NodeFlags; + decorators?: NodeArray; + modifiers?: ModifiersArray; + parent?: Node; + } + interface JSDocContainer { + } + type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + interface NodeArray extends ReadonlyArray, TextRange { + hasTrailingComma?: boolean; + } + interface Token extends Node { + kind: TKind; + } + type DotDotDotToken = Token; + type QuestionToken = Token; + type ColonToken = Token; + type EqualsToken = Token; + type AsteriskToken = Token; + type EqualsGreaterThanToken = Token; + type EndOfFileToken = Token & JSDocContainer; + type AtToken = Token; + type ReadonlyToken = Token; + type AwaitKeywordToken = Token; + type Modifier = Token | Token | Token | Token | Token | Token | Token | Token | Token | Token | Token; + type ModifiersArray = NodeArray; + interface Identifier extends PrimaryExpression { + kind: SyntaxKind.Identifier; + /** + * Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.) + * Text of identifier, but if the identifier begins with two underscores, this will begin with three. + */ + escapedText: __String; + originalKeywordKind?: SyntaxKind; + isInJSDocNamespace?: boolean; + } + interface TransientIdentifier extends Identifier { + resolvedSymbol: Symbol; + } + interface QualifiedName extends Node { + kind: SyntaxKind.QualifiedName; + left: EntityName; + right: Identifier; + } + type EntityName = Identifier | QualifiedName; + type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName; + type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern; + interface Declaration extends Node { + _declarationBrand: any; + } + interface NamedDeclaration extends Declaration { + name?: DeclarationName; + } + interface DeclarationStatement extends NamedDeclaration, Statement { + name?: Identifier | StringLiteral | NumericLiteral; + } + interface ComputedPropertyName extends Node { + kind: SyntaxKind.ComputedPropertyName; + expression: Expression; + } + interface Decorator extends Node { + kind: SyntaxKind.Decorator; + parent?: NamedDeclaration; + expression: LeftHandSideExpression; + } + interface TypeParameterDeclaration extends NamedDeclaration { + kind: SyntaxKind.TypeParameter; + parent?: DeclarationWithTypeParameters; + name: Identifier; + constraint?: TypeNode; + default?: TypeNode; + expression?: Expression; + } + interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { + kind: SignatureDeclaration["kind"]; + name?: PropertyName; + typeParameters?: NodeArray; + parameters: NodeArray; + type: TypeNode | undefined; + } + type SignatureDeclaration = CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction; + interface CallSignatureDeclaration extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.CallSignature; + } + interface ConstructSignatureDeclaration extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.ConstructSignature; + } + type BindingName = Identifier | BindingPattern; + interface VariableDeclaration extends NamedDeclaration { + kind: SyntaxKind.VariableDeclaration; + parent?: VariableDeclarationList | CatchClause; + name: BindingName; + type?: TypeNode; + initializer?: Expression; + } + interface VariableDeclarationList extends Node { + kind: SyntaxKind.VariableDeclarationList; + parent?: VariableStatement | ForStatement | ForOfStatement | ForInStatement; + declarations: NodeArray; + } + interface ParameterDeclaration extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.Parameter; + parent?: SignatureDeclaration; + dotDotDotToken?: DotDotDotToken; + name: BindingName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface BindingElement extends NamedDeclaration { + kind: SyntaxKind.BindingElement; + parent?: BindingPattern; + propertyName?: PropertyName; + dotDotDotToken?: DotDotDotToken; + name: BindingName; + initializer?: Expression; + } + interface PropertySignature extends TypeElement, JSDocContainer { + kind: SyntaxKind.PropertySignature; + name: PropertyName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface PropertyDeclaration extends ClassElement, JSDocContainer { + kind: SyntaxKind.PropertyDeclaration; + questionToken?: QuestionToken; + name: PropertyName; + type?: TypeNode; + initializer?: Expression; + } + interface ObjectLiteralElement extends NamedDeclaration { + _objectLiteralBrandBrand: any; + name?: PropertyName; + } + type ObjectLiteralElementLike = PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | MethodDeclaration | AccessorDeclaration; + interface PropertyAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.PropertyAssignment; + name: PropertyName; + questionToken?: QuestionToken; + initializer: Expression; + } + interface ShorthandPropertyAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.ShorthandPropertyAssignment; + name: Identifier; + questionToken?: QuestionToken; + equalsToken?: Token; + objectAssignmentInitializer?: Expression; + } + interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.SpreadAssignment; + expression: Expression; + } + interface VariableLikeDeclaration extends NamedDeclaration { + propertyName?: PropertyName; + dotDotDotToken?: DotDotDotToken; + name: DeclarationName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface PropertyLikeDeclaration extends NamedDeclaration { + name: PropertyName; + } + interface ObjectBindingPattern extends Node { + kind: SyntaxKind.ObjectBindingPattern; + parent?: VariableDeclaration | ParameterDeclaration | BindingElement; + elements: NodeArray; + } + interface ArrayBindingPattern extends Node { + kind: SyntaxKind.ArrayBindingPattern; + parent?: VariableDeclaration | ParameterDeclaration | BindingElement; + elements: NodeArray; + } + type BindingPattern = ObjectBindingPattern | ArrayBindingPattern; + type ArrayBindingElement = BindingElement | OmittedExpression; + /** + * Several node kinds share function-like features such as a signature, + * a name, and a body. These nodes should extend FunctionLikeDeclarationBase. + * Examples: + * - FunctionDeclaration + * - MethodDeclaration + * - AccessorDeclaration + */ + interface FunctionLikeDeclarationBase extends SignatureDeclarationBase { + _functionLikeDeclarationBrand: any; + asteriskToken?: AsteriskToken; + questionToken?: QuestionToken; + body?: Block | Expression; + } + type FunctionLikeDeclaration = FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionExpression | ArrowFunction; + type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration; + interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement { + kind: SyntaxKind.FunctionDeclaration; + name?: Identifier; + body?: FunctionBody; + } + interface MethodSignature extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.MethodSignature; + name: PropertyName; + } + interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.MethodDeclaration; + name: PropertyName; + body?: FunctionBody; + } + interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { + kind: SyntaxKind.Constructor; + parent?: ClassDeclaration | ClassExpression; + body?: FunctionBody; + } + /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ + interface SemicolonClassElement extends ClassElement { + kind: SyntaxKind.SemicolonClassElement; + parent?: ClassDeclaration | ClassExpression; + } + interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.GetAccessor; + parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + name: PropertyName; + body: FunctionBody; + } + interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.SetAccessor; + parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + name: PropertyName; + body: FunctionBody; + } + type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; + interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { + kind: SyntaxKind.IndexSignature; + parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + } + interface TypeNode extends Node { + _typeNodeBrand: any; + } + interface KeywordTypeNode extends TypeNode { + kind: SyntaxKind.AnyKeyword | SyntaxKind.NumberKeyword | SyntaxKind.ObjectKeyword | SyntaxKind.BooleanKeyword | SyntaxKind.StringKeyword | SyntaxKind.SymbolKeyword | SyntaxKind.ThisKeyword | SyntaxKind.VoidKeyword | SyntaxKind.UndefinedKeyword | SyntaxKind.NullKeyword | SyntaxKind.NeverKeyword; + } + interface ThisTypeNode extends TypeNode { + kind: SyntaxKind.ThisType; + } + type FunctionOrConstructorTypeNode = FunctionTypeNode | ConstructorTypeNode; + interface FunctionTypeNode extends TypeNode, SignatureDeclarationBase { + kind: SyntaxKind.FunctionType; + } + interface ConstructorTypeNode extends TypeNode, SignatureDeclarationBase { + kind: SyntaxKind.ConstructorType; + } + type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments; + interface TypeReferenceNode extends TypeNode { + kind: SyntaxKind.TypeReference; + typeName: EntityName; + typeArguments?: NodeArray; + } + interface TypePredicateNode extends TypeNode { + kind: SyntaxKind.TypePredicate; + parent?: SignatureDeclaration; + parameterName: Identifier | ThisTypeNode; + type: TypeNode; + } + interface TypeQueryNode extends TypeNode { + kind: SyntaxKind.TypeQuery; + exprName: EntityName; + } + interface TypeLiteralNode extends TypeNode, Declaration { + kind: SyntaxKind.TypeLiteral; + members: NodeArray; + } + interface ArrayTypeNode extends TypeNode { + kind: SyntaxKind.ArrayType; + elementType: TypeNode; + } + interface TupleTypeNode extends TypeNode { + kind: SyntaxKind.TupleType; + elementTypes: NodeArray; + } + type UnionOrIntersectionTypeNode = UnionTypeNode | IntersectionTypeNode; + interface UnionTypeNode extends TypeNode { + kind: SyntaxKind.UnionType; + types: NodeArray; + } + interface IntersectionTypeNode extends TypeNode { + kind: SyntaxKind.IntersectionType; + types: NodeArray; + } + interface ParenthesizedTypeNode extends TypeNode { + kind: SyntaxKind.ParenthesizedType; + type: TypeNode; + } + interface TypeOperatorNode extends TypeNode { + kind: SyntaxKind.TypeOperator; + operator: SyntaxKind.KeyOfKeyword; + type: TypeNode; + } + interface IndexedAccessTypeNode extends TypeNode { + kind: SyntaxKind.IndexedAccessType; + objectType: TypeNode; + indexType: TypeNode; + } + interface MappedTypeNode extends TypeNode, Declaration { + kind: SyntaxKind.MappedType; + readonlyToken?: ReadonlyToken; + typeParameter: TypeParameterDeclaration; + questionToken?: QuestionToken; + type?: TypeNode; + } + interface LiteralTypeNode extends TypeNode { + kind: SyntaxKind.LiteralType; + literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression; + } + interface StringLiteral extends LiteralExpression { + kind: SyntaxKind.StringLiteral; + } + interface Expression extends Node { + _expressionBrand: any; + } + interface OmittedExpression extends Expression { + kind: SyntaxKind.OmittedExpression; + } + interface PartiallyEmittedExpression extends LeftHandSideExpression { + kind: SyntaxKind.PartiallyEmittedExpression; + expression: Expression; + } + interface UnaryExpression extends Expression { + _unaryExpressionBrand: any; + } + /** Deprecated, please use UpdateExpression */ + type IncrementExpression = UpdateExpression; + interface UpdateExpression extends UnaryExpression { + _updateExpressionBrand: any; + } + type PrefixUnaryOperator = SyntaxKind.PlusPlusToken | SyntaxKind.MinusMinusToken | SyntaxKind.PlusToken | SyntaxKind.MinusToken | SyntaxKind.TildeToken | SyntaxKind.ExclamationToken; + interface PrefixUnaryExpression extends UpdateExpression { + kind: SyntaxKind.PrefixUnaryExpression; + operator: PrefixUnaryOperator; + operand: UnaryExpression; + } + type PostfixUnaryOperator = SyntaxKind.PlusPlusToken | SyntaxKind.MinusMinusToken; + interface PostfixUnaryExpression extends UpdateExpression { + kind: SyntaxKind.PostfixUnaryExpression; + operand: LeftHandSideExpression; + operator: PostfixUnaryOperator; + } + interface LeftHandSideExpression extends UpdateExpression { + _leftHandSideExpressionBrand: any; + } + interface MemberExpression extends LeftHandSideExpression { + _memberExpressionBrand: any; + } + interface PrimaryExpression extends MemberExpression { + _primaryExpressionBrand: any; + } + interface NullLiteral extends PrimaryExpression, TypeNode { + kind: SyntaxKind.NullKeyword; + } + interface BooleanLiteral extends PrimaryExpression, TypeNode { + kind: SyntaxKind.TrueKeyword | SyntaxKind.FalseKeyword; + } + interface ThisExpression extends PrimaryExpression, KeywordTypeNode { + kind: SyntaxKind.ThisKeyword; + } + interface SuperExpression extends PrimaryExpression { + kind: SyntaxKind.SuperKeyword; + } + interface ImportExpression extends PrimaryExpression { + kind: SyntaxKind.ImportKeyword; + } + interface DeleteExpression extends UnaryExpression { + kind: SyntaxKind.DeleteExpression; + expression: UnaryExpression; + } + interface TypeOfExpression extends UnaryExpression { + kind: SyntaxKind.TypeOfExpression; + expression: UnaryExpression; + } + interface VoidExpression extends UnaryExpression { + kind: SyntaxKind.VoidExpression; + expression: UnaryExpression; + } + interface AwaitExpression extends UnaryExpression { + kind: SyntaxKind.AwaitExpression; + expression: UnaryExpression; + } + interface YieldExpression extends Expression { + kind: SyntaxKind.YieldExpression; + asteriskToken?: AsteriskToken; + expression?: Expression; + } + type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken; + type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken; + type MultiplicativeOperatorOrHigher = ExponentiationOperator | MultiplicativeOperator; + type AdditiveOperator = SyntaxKind.PlusToken | SyntaxKind.MinusToken; + type AdditiveOperatorOrHigher = MultiplicativeOperatorOrHigher | AdditiveOperator; + type ShiftOperator = SyntaxKind.LessThanLessThanToken | SyntaxKind.GreaterThanGreaterThanToken | SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + type ShiftOperatorOrHigher = AdditiveOperatorOrHigher | ShiftOperator; + type RelationalOperator = SyntaxKind.LessThanToken | SyntaxKind.LessThanEqualsToken | SyntaxKind.GreaterThanToken | SyntaxKind.GreaterThanEqualsToken | SyntaxKind.InstanceOfKeyword | SyntaxKind.InKeyword; + type RelationalOperatorOrHigher = ShiftOperatorOrHigher | RelationalOperator; + type EqualityOperator = SyntaxKind.EqualsEqualsToken | SyntaxKind.EqualsEqualsEqualsToken | SyntaxKind.ExclamationEqualsEqualsToken | SyntaxKind.ExclamationEqualsToken; + type EqualityOperatorOrHigher = RelationalOperatorOrHigher | EqualityOperator; + type BitwiseOperator = SyntaxKind.AmpersandToken | SyntaxKind.BarToken | SyntaxKind.CaretToken; + type BitwiseOperatorOrHigher = EqualityOperatorOrHigher | BitwiseOperator; + type LogicalOperator = SyntaxKind.AmpersandAmpersandToken | SyntaxKind.BarBarToken; + type LogicalOperatorOrHigher = BitwiseOperatorOrHigher | LogicalOperator; + type CompoundAssignmentOperator = SyntaxKind.PlusEqualsToken | SyntaxKind.MinusEqualsToken | SyntaxKind.AsteriskAsteriskEqualsToken | SyntaxKind.AsteriskEqualsToken | SyntaxKind.SlashEqualsToken | SyntaxKind.PercentEqualsToken | SyntaxKind.AmpersandEqualsToken | SyntaxKind.BarEqualsToken | SyntaxKind.CaretEqualsToken | SyntaxKind.LessThanLessThanEqualsToken | SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken | SyntaxKind.GreaterThanGreaterThanEqualsToken; + type AssignmentOperator = SyntaxKind.EqualsToken | CompoundAssignmentOperator; + type AssignmentOperatorOrHigher = LogicalOperatorOrHigher | AssignmentOperator; + type BinaryOperator = AssignmentOperatorOrHigher | SyntaxKind.CommaToken; + type BinaryOperatorToken = Token; + interface BinaryExpression extends Expression, Declaration { + kind: SyntaxKind.BinaryExpression; + left: Expression; + operatorToken: BinaryOperatorToken; + right: Expression; + } + type AssignmentOperatorToken = Token; + interface AssignmentExpression extends BinaryExpression { + left: LeftHandSideExpression; + operatorToken: TOperator; + } + interface ObjectDestructuringAssignment extends AssignmentExpression { + left: ObjectLiteralExpression; + } + interface ArrayDestructuringAssignment extends AssignmentExpression { + left: ArrayLiteralExpression; + } + type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; + type BindingOrAssignmentElement = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | OmittedExpression | SpreadElement | ArrayLiteralExpression | ObjectLiteralExpression | AssignmentExpression | Identifier | PropertyAccessExpression | ElementAccessExpression; + type BindingOrAssignmentElementRestIndicator = DotDotDotToken | SpreadElement | SpreadAssignment; + type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + type ObjectBindingOrAssignmentPattern = ObjectBindingPattern | ObjectLiteralExpression; + type ArrayBindingOrAssignmentPattern = ArrayBindingPattern | ArrayLiteralExpression; + type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression; + type BindingOrAssignmentPattern = ObjectBindingOrAssignmentPattern | ArrayBindingOrAssignmentPattern; + interface ConditionalExpression extends Expression { + kind: SyntaxKind.ConditionalExpression; + condition: Expression; + questionToken: QuestionToken; + whenTrue: Expression; + colonToken: ColonToken; + whenFalse: Expression; + } + type FunctionBody = Block; + type ConciseBody = FunctionBody | Expression; + interface FunctionExpression extends PrimaryExpression, FunctionLikeDeclarationBase, JSDocContainer { + kind: SyntaxKind.FunctionExpression; + name?: Identifier; + body: FunctionBody; + } + interface ArrowFunction extends Expression, FunctionLikeDeclarationBase, JSDocContainer { + kind: SyntaxKind.ArrowFunction; + equalsGreaterThanToken: EqualsGreaterThanToken; + body: ConciseBody; + } + interface LiteralLikeNode extends Node { + text: string; + isUnterminated?: boolean; + hasExtendedUnicodeEscape?: boolean; + } + interface LiteralExpression extends LiteralLikeNode, PrimaryExpression { + _literalExpressionBrand: any; + } + interface RegularExpressionLiteral extends LiteralExpression { + kind: SyntaxKind.RegularExpressionLiteral; + } + interface NoSubstitutionTemplateLiteral extends LiteralExpression { + kind: SyntaxKind.NoSubstitutionTemplateLiteral; + } + interface NumericLiteral extends LiteralExpression { + kind: SyntaxKind.NumericLiteral; + } + interface TemplateHead extends LiteralLikeNode { + kind: SyntaxKind.TemplateHead; + parent?: TemplateExpression; + } + interface TemplateMiddle extends LiteralLikeNode { + kind: SyntaxKind.TemplateMiddle; + parent?: TemplateSpan; + } + interface TemplateTail extends LiteralLikeNode { + kind: SyntaxKind.TemplateTail; + parent?: TemplateSpan; + } + type TemplateLiteral = TemplateExpression | NoSubstitutionTemplateLiteral; + interface TemplateExpression extends PrimaryExpression { + kind: SyntaxKind.TemplateExpression; + head: TemplateHead; + templateSpans: NodeArray; + } + interface TemplateSpan extends Node { + kind: SyntaxKind.TemplateSpan; + parent?: TemplateExpression; + expression: Expression; + literal: TemplateMiddle | TemplateTail; + } + interface ParenthesizedExpression extends PrimaryExpression, JSDocContainer { + kind: SyntaxKind.ParenthesizedExpression; + expression: Expression; + } + interface ArrayLiteralExpression extends PrimaryExpression { + kind: SyntaxKind.ArrayLiteralExpression; + elements: NodeArray; + } + interface SpreadElement extends Expression { + kind: SyntaxKind.SpreadElement; + parent?: ArrayLiteralExpression | CallExpression | NewExpression; + expression: Expression; + } + /** + * This interface is a base interface for ObjectLiteralExpression and JSXAttributes to extend from. JSXAttributes is similar to + * ObjectLiteralExpression in that it contains array of properties; however, JSXAttributes' properties can only be + * JSXAttribute or JSXSpreadAttribute. ObjectLiteralExpression, on the other hand, can only have properties of type + * ObjectLiteralElement (e.g. PropertyAssignment, ShorthandPropertyAssignment etc.) + */ + interface ObjectLiteralExpressionBase extends PrimaryExpression, Declaration { + properties: NodeArray; + } + interface ObjectLiteralExpression extends ObjectLiteralExpressionBase { + kind: SyntaxKind.ObjectLiteralExpression; + } + type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression | ParenthesizedExpression; + type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { + kind: SyntaxKind.PropertyAccessExpression; + expression: LeftHandSideExpression; + name: Identifier; + } + interface SuperPropertyAccessExpression extends PropertyAccessExpression { + expression: SuperExpression; + } + /** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */ + interface PropertyAccessEntityNameExpression extends PropertyAccessExpression { + _propertyAccessExpressionLikeQualifiedNameBrand?: any; + expression: EntityNameExpression; + } + interface ElementAccessExpression extends MemberExpression { + kind: SyntaxKind.ElementAccessExpression; + expression: LeftHandSideExpression; + argumentExpression?: Expression; + } + interface SuperElementAccessExpression extends ElementAccessExpression { + expression: SuperExpression; + } + type SuperProperty = SuperPropertyAccessExpression | SuperElementAccessExpression; + interface CallExpression extends LeftHandSideExpression, Declaration { + kind: SyntaxKind.CallExpression; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + arguments: NodeArray; + } + interface SuperCall extends CallExpression { + expression: SuperExpression; + } + interface ImportCall extends CallExpression { + expression: ImportExpression; + } + interface ExpressionWithTypeArguments extends TypeNode { + kind: SyntaxKind.ExpressionWithTypeArguments; + parent?: HeritageClause; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + } + interface NewExpression extends PrimaryExpression, Declaration { + kind: SyntaxKind.NewExpression; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + arguments?: NodeArray; + } + interface TaggedTemplateExpression extends MemberExpression { + kind: SyntaxKind.TaggedTemplateExpression; + tag: LeftHandSideExpression; + template: TemplateLiteral; + } + type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; + interface AsExpression extends Expression { + kind: SyntaxKind.AsExpression; + expression: Expression; + type: TypeNode; + } + interface TypeAssertion extends UnaryExpression { + kind: SyntaxKind.TypeAssertionExpression; + type: TypeNode; + expression: UnaryExpression; + } + type AssertionExpression = TypeAssertion | AsExpression; + interface NonNullExpression extends LeftHandSideExpression { + kind: SyntaxKind.NonNullExpression; + expression: Expression; + } + interface MetaProperty extends PrimaryExpression { + kind: SyntaxKind.MetaProperty; + keywordToken: SyntaxKind.NewKeyword; + name: Identifier; + } + interface JsxElement extends PrimaryExpression { + kind: SyntaxKind.JsxElement; + openingElement: JsxOpeningElement; + children: NodeArray; + closingElement: JsxClosingElement; + } + type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement; + type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute; + type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression; + interface JsxAttributes extends ObjectLiteralExpressionBase { + parent?: JsxOpeningLikeElement; + } + interface JsxOpeningElement extends Expression { + kind: SyntaxKind.JsxOpeningElement; + parent?: JsxElement; + tagName: JsxTagNameExpression; + attributes: JsxAttributes; + } + interface JsxSelfClosingElement extends PrimaryExpression { + kind: SyntaxKind.JsxSelfClosingElement; + tagName: JsxTagNameExpression; + attributes: JsxAttributes; + } + interface JsxAttribute extends ObjectLiteralElement { + kind: SyntaxKind.JsxAttribute; + parent?: JsxAttributes; + name: Identifier; + initializer?: StringLiteral | JsxExpression; + } + interface JsxSpreadAttribute extends ObjectLiteralElement { + kind: SyntaxKind.JsxSpreadAttribute; + parent?: JsxAttributes; + expression: Expression; + } + interface JsxClosingElement extends Node { + kind: SyntaxKind.JsxClosingElement; + parent?: JsxElement; + tagName: JsxTagNameExpression; + } + interface JsxExpression extends Expression { + kind: SyntaxKind.JsxExpression; + parent?: JsxElement | JsxAttributeLike; + dotDotDotToken?: Token; + expression?: Expression; + } + interface JsxText extends Node { + kind: SyntaxKind.JsxText; + containsOnlyWhiteSpaces: boolean; + parent?: JsxElement; + } + type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement; + interface Statement extends Node { + _statementBrand: any; + } + interface NotEmittedStatement extends Statement { + kind: SyntaxKind.NotEmittedStatement; + } + /** + * A list of comma-seperated expressions. This node is only created by transformations. + */ + interface CommaListExpression extends Expression { + kind: SyntaxKind.CommaListExpression; + elements: NodeArray; + } + interface EmptyStatement extends Statement { + kind: SyntaxKind.EmptyStatement; + } + interface DebuggerStatement extends Statement { + kind: SyntaxKind.DebuggerStatement; + } + interface MissingDeclaration extends DeclarationStatement, ClassElement, ObjectLiteralElement, TypeElement { + kind: SyntaxKind.MissingDeclaration; + name?: Identifier; + } + type BlockLike = SourceFile | Block | ModuleBlock | CaseOrDefaultClause; + interface Block extends Statement { + kind: SyntaxKind.Block; + statements: NodeArray; + } + interface VariableStatement extends Statement, JSDocContainer { + kind: SyntaxKind.VariableStatement; + declarationList: VariableDeclarationList; + } + interface ExpressionStatement extends Statement, JSDocContainer { + kind: SyntaxKind.ExpressionStatement; + expression: Expression; + } + interface IfStatement extends Statement { + kind: SyntaxKind.IfStatement; + expression: Expression; + thenStatement: Statement; + elseStatement?: Statement; + } + interface IterationStatement extends Statement { + statement: Statement; + } + interface DoStatement extends IterationStatement { + kind: SyntaxKind.DoStatement; + expression: Expression; + } + interface WhileStatement extends IterationStatement { + kind: SyntaxKind.WhileStatement; + expression: Expression; + } + type ForInitializer = VariableDeclarationList | Expression; + interface ForStatement extends IterationStatement { + kind: SyntaxKind.ForStatement; + initializer?: ForInitializer; + condition?: Expression; + incrementor?: Expression; + } + type ForInOrOfStatement = ForInStatement | ForOfStatement; + interface ForInStatement extends IterationStatement { + kind: SyntaxKind.ForInStatement; + initializer: ForInitializer; + expression: Expression; + } + interface ForOfStatement extends IterationStatement { + kind: SyntaxKind.ForOfStatement; + awaitModifier?: AwaitKeywordToken; + initializer: ForInitializer; + expression: Expression; + } + interface BreakStatement extends Statement { + kind: SyntaxKind.BreakStatement; + label?: Identifier; + } + interface ContinueStatement extends Statement { + kind: SyntaxKind.ContinueStatement; + label?: Identifier; + } + type BreakOrContinueStatement = BreakStatement | ContinueStatement; + interface ReturnStatement extends Statement { + kind: SyntaxKind.ReturnStatement; + expression?: Expression; + } + interface WithStatement extends Statement { + kind: SyntaxKind.WithStatement; + expression: Expression; + statement: Statement; + } + interface SwitchStatement extends Statement { + kind: SyntaxKind.SwitchStatement; + expression: Expression; + caseBlock: CaseBlock; + possiblyExhaustive?: boolean; + } + interface CaseBlock extends Node { + kind: SyntaxKind.CaseBlock; + parent?: SwitchStatement; + clauses: NodeArray; + } + interface CaseClause extends Node { + kind: SyntaxKind.CaseClause; + parent?: CaseBlock; + expression: Expression; + statements: NodeArray; + } + interface DefaultClause extends Node { + kind: SyntaxKind.DefaultClause; + parent?: CaseBlock; + statements: NodeArray; + } + type CaseOrDefaultClause = CaseClause | DefaultClause; + interface LabeledStatement extends Statement, JSDocContainer { + kind: SyntaxKind.LabeledStatement; + label: Identifier; + statement: Statement; + } + interface ThrowStatement extends Statement { + kind: SyntaxKind.ThrowStatement; + expression: Expression; + } + interface TryStatement extends Statement { + kind: SyntaxKind.TryStatement; + tryBlock: Block; + catchClause?: CatchClause; + finallyBlock?: Block; + } + interface CatchClause extends Node { + kind: SyntaxKind.CatchClause; + parent?: TryStatement; + variableDeclaration?: VariableDeclaration; + block: Block; + } + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; + interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; + name?: Identifier; + typeParameters?: NodeArray; + heritageClauses?: NodeArray; + members: NodeArray; + } + interface ClassDeclaration extends ClassLikeDeclarationBase, DeclarationStatement { + kind: SyntaxKind.ClassDeclaration; + name?: Identifier; + } + interface ClassExpression extends ClassLikeDeclarationBase, PrimaryExpression { + kind: SyntaxKind.ClassExpression; + } + type ClassLikeDeclaration = ClassDeclaration | ClassExpression; + interface ClassElement extends NamedDeclaration { + _classElementBrand: any; + name?: PropertyName; + } + interface TypeElement extends NamedDeclaration { + _typeElementBrand: any; + name?: PropertyName; + questionToken?: QuestionToken; + } + interface InterfaceDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.InterfaceDeclaration; + name: Identifier; + typeParameters?: NodeArray; + heritageClauses?: NodeArray; + members: NodeArray; + } + interface HeritageClause extends Node { + kind: SyntaxKind.HeritageClause; + parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; + types: NodeArray; + } + interface TypeAliasDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.TypeAliasDeclaration; + name: Identifier; + typeParameters?: NodeArray; + type: TypeNode; + } + interface EnumMember extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.EnumMember; + parent?: EnumDeclaration; + name: PropertyName; + initializer?: Expression; + } + interface EnumDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.EnumDeclaration; + name: Identifier; + members: NodeArray; + } + type ModuleName = Identifier | StringLiteral; + type ModuleBody = NamespaceBody | JSDocNamespaceBody; + interface ModuleDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.ModuleDeclaration; + parent?: ModuleBody | SourceFile; + name: ModuleName; + body?: ModuleBody | JSDocNamespaceDeclaration; + } + type NamespaceBody = ModuleBlock | NamespaceDeclaration; + interface NamespaceDeclaration extends ModuleDeclaration { + name: Identifier; + body: NamespaceBody; + } + type JSDocNamespaceBody = Identifier | JSDocNamespaceDeclaration; + interface JSDocNamespaceDeclaration extends ModuleDeclaration { + name: Identifier; + body: JSDocNamespaceBody; + } + interface ModuleBlock extends Node, Statement { + kind: SyntaxKind.ModuleBlock; + parent?: ModuleDeclaration; + statements: NodeArray; + } + type ModuleReference = EntityName | ExternalModuleReference; + /** + * One of: + * - import x = require("mod"); + * - import x = M.x; + */ + interface ImportEqualsDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.ImportEqualsDeclaration; + parent?: SourceFile | ModuleBlock; + name: Identifier; + moduleReference: ModuleReference; + } + interface ExternalModuleReference extends Node { + kind: SyntaxKind.ExternalModuleReference; + parent?: ImportEqualsDeclaration; + expression?: Expression; + } + interface ImportDeclaration extends Statement { + kind: SyntaxKind.ImportDeclaration; + parent?: SourceFile | ModuleBlock; + importClause?: ImportClause; + /** If this is not a StringLiteral it will be a grammar error. */ + moduleSpecifier: Expression; + } + type NamedImportBindings = NamespaceImport | NamedImports; + interface ImportClause extends NamedDeclaration { + kind: SyntaxKind.ImportClause; + parent?: ImportDeclaration; + name?: Identifier; + namedBindings?: NamedImportBindings; + } + interface NamespaceImport extends NamedDeclaration { + kind: SyntaxKind.NamespaceImport; + parent?: ImportClause; + name: Identifier; + } + interface NamespaceExportDeclaration extends DeclarationStatement { + kind: SyntaxKind.NamespaceExportDeclaration; + name: Identifier; + } + interface ExportDeclaration extends DeclarationStatement { + kind: SyntaxKind.ExportDeclaration; + parent?: SourceFile | ModuleBlock; + exportClause?: NamedExports; + /** If this is not a StringLiteral it will be a grammar error. */ + moduleSpecifier?: Expression; + } + interface NamedImports extends Node { + kind: SyntaxKind.NamedImports; + parent?: ImportClause; + elements: NodeArray; + } + interface NamedExports extends Node { + kind: SyntaxKind.NamedExports; + parent?: ExportDeclaration; + elements: NodeArray; + } + type NamedImportsOrExports = NamedImports | NamedExports; + interface ImportSpecifier extends NamedDeclaration { + kind: SyntaxKind.ImportSpecifier; + parent?: NamedImports; + propertyName?: Identifier; + name: Identifier; + } + interface ExportSpecifier extends NamedDeclaration { + kind: SyntaxKind.ExportSpecifier; + parent?: NamedExports; + propertyName?: Identifier; + name: Identifier; + } + type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier; + interface ExportAssignment extends DeclarationStatement { + kind: SyntaxKind.ExportAssignment; + parent?: SourceFile; + isExportEquals?: boolean; + expression: Expression; + } + interface FileReference extends TextRange { + fileName: string; + } + interface CheckJsDirective extends TextRange { + enabled: boolean; + } + type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia; + interface CommentRange extends TextRange { + hasTrailingNewLine?: boolean; + kind: CommentKind; + } + interface SynthesizedComment extends CommentRange { + text: string; + pos: -1; + end: -1; + } + interface JSDocTypeExpression extends TypeNode { + kind: SyntaxKind.JSDocTypeExpression; + type: TypeNode; + } + interface JSDocType extends TypeNode { + _jsDocTypeBrand: any; + } + interface JSDocAllType extends JSDocType { + kind: SyntaxKind.JSDocAllType; + } + interface JSDocUnknownType extends JSDocType { + kind: SyntaxKind.JSDocUnknownType; + } + interface JSDocNonNullableType extends JSDocType { + kind: SyntaxKind.JSDocNonNullableType; + type: TypeNode; + } + interface JSDocNullableType extends JSDocType { + kind: SyntaxKind.JSDocNullableType; + type: TypeNode; + } + interface JSDocOptionalType extends JSDocType { + kind: SyntaxKind.JSDocOptionalType; + type: TypeNode; + } + interface JSDocFunctionType extends JSDocType, SignatureDeclarationBase { + kind: SyntaxKind.JSDocFunctionType; + } + interface JSDocVariadicType extends JSDocType { + kind: SyntaxKind.JSDocVariadicType; + type: TypeNode; + } + type JSDocTypeReferencingNode = JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; + interface JSDoc extends Node { + kind: SyntaxKind.JSDocComment; + parent?: HasJSDoc; + tags: NodeArray | undefined; + comment: string | undefined; + } + interface JSDocTag extends Node { + parent: JSDoc; + atToken: AtToken; + tagName: Identifier; + comment: string | undefined; + } + interface JSDocUnknownTag extends JSDocTag { + kind: SyntaxKind.JSDocTag; + } + interface JSDocAugmentsTag extends JSDocTag { + kind: SyntaxKind.JSDocAugmentsTag; + class: ExpressionWithTypeArguments & { + expression: Identifier | PropertyAccessEntityNameExpression; + }; + } + interface JSDocClassTag extends JSDocTag { + kind: SyntaxKind.JSDocClassTag; + } + interface JSDocTemplateTag extends JSDocTag { + kind: SyntaxKind.JSDocTemplateTag; + typeParameters: NodeArray; + } + interface JSDocReturnTag extends JSDocTag { + kind: SyntaxKind.JSDocReturnTag; + typeExpression: JSDocTypeExpression; + } + interface JSDocTypeTag extends JSDocTag { + kind: SyntaxKind.JSDocTypeTag; + typeExpression: JSDocTypeExpression; + } + interface JSDocTypedefTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocTypedefTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; + typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; + } + interface JSDocPropertyLikeTag extends JSDocTag, Declaration { + parent: JSDoc; + name: EntityName; + typeExpression: JSDocTypeExpression; + /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ + isNameFirst: boolean; + isBracketed: boolean; + } + interface JSDocPropertyTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocPropertyTag; + } + interface JSDocParameterTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocParameterTag; + } + interface JSDocTypeLiteral extends JSDocType { + kind: SyntaxKind.JSDocTypeLiteral; + jsDocPropertyTags?: ReadonlyArray; + /** If true, then this type literal represents an *array* of its type. */ + isArrayType?: boolean; + } + enum FlowFlags { + Unreachable = 1, + Start = 2, + BranchLabel = 4, + LoopLabel = 8, + Assignment = 16, + TrueCondition = 32, + FalseCondition = 64, + SwitchClause = 128, + ArrayMutation = 256, + Referenced = 512, + Shared = 1024, + PreFinally = 2048, + AfterFinally = 4096, + Label = 12, + Condition = 96, + } + interface FlowLock { + locked?: boolean; + } + interface AfterFinallyFlow extends FlowNodeBase, FlowLock { + antecedent: FlowNode; + } + interface PreFinallyFlow extends FlowNodeBase { + antecedent: FlowNode; + lock: FlowLock; + } + type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation; + interface FlowNodeBase { + flags: FlowFlags; + id?: number; + } + interface FlowStart extends FlowNodeBase { + container?: FunctionExpression | ArrowFunction | MethodDeclaration; + } + interface FlowLabel extends FlowNodeBase { + antecedents: FlowNode[]; + } + interface FlowAssignment extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; + } + interface FlowCondition extends FlowNodeBase { + expression: Expression; + antecedent: FlowNode; + } + interface FlowSwitchClause extends FlowNodeBase { + switchStatement: SwitchStatement; + clauseStart: number; + clauseEnd: number; + antecedent: FlowNode; + } + interface FlowArrayMutation extends FlowNodeBase { + node: CallExpression | BinaryExpression; + antecedent: FlowNode; + } + type FlowType = Type | IncompleteType; + interface IncompleteType { + flags: TypeFlags; + type: Type; + } + interface AmdDependency { + path: string; + name: string; + } + interface SourceFile extends Declaration { + kind: SyntaxKind.SourceFile; + statements: NodeArray; + endOfFileToken: Token; + fileName: string; + text: string; + amdDependencies: ReadonlyArray; + moduleName: string; + referencedFiles: ReadonlyArray; + typeReferenceDirectives: ReadonlyArray; + languageVariant: LanguageVariant; + isDeclarationFile: boolean; + /** + * lib.d.ts should have a reference comment like + * + * /// + * + * If any other file has this comment, it signals not to include lib.d.ts + * because this containing file is intended to act as a default library. + */ + hasNoDefaultLib: boolean; + languageVersion: ScriptTarget; + } + interface Bundle extends Node { + kind: SyntaxKind.Bundle; + sourceFiles: ReadonlyArray; + } + interface JsonSourceFile extends SourceFile { + jsonObject?: ObjectLiteralExpression; + extendedSourceFiles?: string[]; + } + interface ScriptReferenceHost { + getCompilerOptions(): CompilerOptions; + getSourceFile(fileName: string): SourceFile; + getSourceFileByPath(path: Path): SourceFile; + getCurrentDirectory(): string; + } + interface ParseConfigHost { + useCaseSensitiveFileNames: boolean; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; + /** + * Gets a value indicating whether the specified path exists and is a file. + * @param path The path to test. + */ + fileExists(path: string): boolean; + readFile(path: string): string | undefined; + } + interface WriteFileCallback { + (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles: ReadonlyArray): void; + } + class OperationCanceledException { + } + interface CancellationToken { + isCancellationRequested(): boolean; + /** @throws OperationCanceledException if isCancellationRequested is true */ + throwIfCancellationRequested(): void; + } + interface Program extends ScriptReferenceHost { + /** + * Get a list of root file names that were passed to a 'createProgram' + */ + getRootFileNames(): ReadonlyArray; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. If targetSourceFile is not specified, then + * the JavaScript and declaration files will be produced for all the files in this program. + * If targetSourceFile is specified, then only the JavaScript and declaration for that + * specific file will be generated. + * + * If writeFile is not specified then the writeFile callback from the compiler host will be + * used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter + * will be invoked when writing the JavaScript and declaration files. + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Gets a type checker that can be used to semantically analyze source files in the program. + */ + getTypeChecker(): TypeChecker; + isSourceFileFromExternalLibrary(file: SourceFile): boolean; + } + interface CustomTransformers { + /** Custom transformers to evaluate before built-in transformations. */ + before?: TransformerFactory[]; + /** Custom transformers to evaluate after built-in transformations. */ + after?: TransformerFactory[]; + } + interface SourceMapSpan { + /** Line number in the .js file. */ + emittedLine: number; + /** Column number in the .js file. */ + emittedColumn: number; + /** Line number in the .ts file. */ + sourceLine: number; + /** Column number in the .ts file. */ + sourceColumn: number; + /** Optional name (index into names array) associated with this span. */ + nameIndex?: number; + /** .ts file (index into sources array) associated with this span */ + sourceIndex: number; + } + interface SourceMapData { + sourceMapFilePath: string; + jsSourceMappingURL: string; + sourceMapFile: string; + sourceMapSourceRoot: string; + sourceMapSources: string[]; + sourceMapSourcesContent?: string[]; + inputSourceFileNames: string[]; + sourceMapNames?: string[]; + sourceMapMappings: string; + sourceMapDecodedMappings: SourceMapSpan[]; + } + /** Return code used by getEmitOutput function to indicate status of the function */ + enum ExitStatus { + Success = 0, + DiagnosticsPresent_OutputsSkipped = 1, + DiagnosticsPresent_OutputsGenerated = 2, + } + interface EmitResult { + emitSkipped: boolean; + /** Contains declaration emit diagnostics */ + diagnostics: ReadonlyArray; + emittedFiles: string[]; + } + interface TypeChecker { + getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; + getDeclaredTypeOfSymbol(symbol: Symbol): Type; + getPropertiesOfType(type: Type): Symbol[]; + getPropertyOfType(type: Type, propertyName: string): Symbol | undefined; + getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined; + getSignaturesOfType(type: Type, kind: SignatureKind): Signature[]; + getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined; + getBaseTypes(type: InterfaceType): BaseType[]; + getBaseTypeOfLiteralType(type: Type): Type; + getWidenedType(type: Type): Type; + getReturnTypeOfSignature(signature: Signature): Type; + getNullableType(type: Type, flags: TypeFlags): Type; + getNonNullableType(type: Type): Type; + /** Note that the resulting nodes cannot be checked. */ + typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; + /** Note that the resulting nodes cannot be checked. */ + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; + getSymbolAtLocation(node: Node): Symbol | undefined; + getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; + getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined; + getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined; + /** + * If a symbol is a local symbol with an associated exported symbol, returns the exported symbol. + * Otherwise returns its input. + * For example, at `export type T = number;`: + * - `getSymbolAtLocation` at the location `T` will return the exported symbol for `T`. + * - But the result of `getSymbolsInScope` will contain the *local* symbol for `T`, not the exported symbol. + * - Calling `getExportSymbolOfSymbol` on that local symbol will return the exported symbol. + */ + getExportSymbolOfSymbol(symbol: Symbol): Symbol; + getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeAtLocation(node: Node): Type; + getTypeFromTypeNode(node: TypeNode): Type; + signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; + typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; + symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + getSymbolDisplayBuilder(): SymbolDisplayBuilder; + getFullyQualifiedName(symbol: Symbol): string; + getAugmentedPropertiesOfType(type: Type): Symbol[]; + getRootSymbols(symbol: Symbol): Symbol[]; + getContextualType(node: Expression): Type | undefined; + /** + * returns unknownSignature in the case of an error. + * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. + */ + getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature; + getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined; + isImplementationOfOverload(node: FunctionLike): boolean | undefined; + isUndefinedSymbol(symbol: Symbol): boolean; + isArgumentsSymbol(symbol: Symbol): boolean; + isUnknownSymbol(symbol: Symbol): boolean; + getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; + isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; + /** Follow all aliases to get the original symbol. */ + getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfModule(moduleSymbol: Symbol): Symbol[]; + getAllAttributesTypeFromJsxOpeningLikeElement(elementNode: JsxOpeningLikeElement): Type | undefined; + getJsxIntrinsicTagNames(): Symbol[]; + isOptionalParameter(node: ParameterDeclaration): boolean; + getAmbientModules(): Symbol[]; + tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; + getApparentType(type: Type): Type; + getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; + getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; + } + enum NodeBuilderFlags { + None = 0, + NoTruncation = 1, + WriteArrayAsGenericType = 2, + WriteTypeArgumentsOfSignature = 32, + UseFullyQualifiedType = 64, + SuppressAnyReturnType = 256, + WriteTypeParametersInQualifiedName = 512, + AllowThisInObjectLiteral = 1024, + AllowQualifedNameInPlaceOfIdentifier = 2048, + AllowAnonymousIdentifier = 8192, + AllowEmptyUnionOrIntersection = 16384, + AllowEmptyTuple = 32768, + IgnoreErrors = 60416, + InObjectTypeLiteral = 1048576, + InTypeAlias = 8388608, + } + interface SymbolDisplayBuilder { + buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; + buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; + buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; + buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + } + interface SymbolWriter { + writeKeyword(text: string): void; + writeOperator(text: string): void; + writePunctuation(text: string): void; + writeSpace(text: string): void; + writeStringLiteral(text: string): void; + writeParameter(text: string): void; + writeProperty(text: string): void; + writeSymbol(text: string, symbol: Symbol): void; + writeLine(): void; + increaseIndent(): void; + decreaseIndent(): void; + clear(): void; + trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError(): void; + reportPrivateInBaseOfClassExpression(propertyName: string): void; + } + enum TypeFormatFlags { + None = 0, + WriteArrayAsGenericType = 1, + UseTypeOfFunction = 4, + NoTruncation = 8, + WriteArrowStyleSignature = 16, + WriteOwnNameForAnyLike = 32, + WriteTypeArgumentsOfSignature = 64, + InElementType = 128, + UseFullyQualifiedType = 256, + InFirstTypeArgument = 512, + InTypeAlias = 1024, + SuppressAnyReturnType = 4096, + AddUndefined = 8192, + WriteClassExpressionAsTypeLiteral = 16384, + InArrayType = 32768, + UseAliasDefinedOutsideCurrentScope = 65536, + } + enum SymbolFormatFlags { + None = 0, + WriteTypeParametersOrArguments = 1, + UseOnlyExternalAliasing = 2, + } + enum TypePredicateKind { + This = 0, + Identifier = 1, + } + interface TypePredicateBase { + kind: TypePredicateKind; + type: Type; + } + interface ThisTypePredicate extends TypePredicateBase { + kind: TypePredicateKind.This; + } + interface IdentifierTypePredicate extends TypePredicateBase { + kind: TypePredicateKind.Identifier; + parameterName: string; + parameterIndex: number; + } + type TypePredicate = IdentifierTypePredicate | ThisTypePredicate; + enum SymbolFlags { + None = 0, + FunctionScopedVariable = 1, + BlockScopedVariable = 2, + Property = 4, + EnumMember = 8, + Function = 16, + Class = 32, + Interface = 64, + ConstEnum = 128, + RegularEnum = 256, + ValueModule = 512, + NamespaceModule = 1024, + TypeLiteral = 2048, + ObjectLiteral = 4096, + Method = 8192, + Constructor = 16384, + GetAccessor = 32768, + SetAccessor = 65536, + Signature = 131072, + TypeParameter = 262144, + TypeAlias = 524288, + ExportValue = 1048576, + Alias = 2097152, + Prototype = 4194304, + ExportStar = 8388608, + Optional = 16777216, + Transient = 33554432, + Enum = 384, + Variable = 3, + Value = 107455, + Type = 793064, + Namespace = 1920, + Module = 1536, + Accessor = 98304, + FunctionScopedVariableExcludes = 107454, + BlockScopedVariableExcludes = 107455, + ParameterExcludes = 107455, + PropertyExcludes = 0, + EnumMemberExcludes = 900095, + FunctionExcludes = 106927, + ClassExcludes = 899519, + InterfaceExcludes = 792968, + RegularEnumExcludes = 899327, + ConstEnumExcludes = 899967, + ValueModuleExcludes = 106639, + NamespaceModuleExcludes = 0, + MethodExcludes = 99263, + GetAccessorExcludes = 41919, + SetAccessorExcludes = 74687, + TypeParameterExcludes = 530920, + TypeAliasExcludes = 793064, + AliasExcludes = 2097152, + ModuleMember = 2623475, + ExportHasLocal = 944, + HasExports = 1952, + HasMembers = 6240, + BlockScoped = 418, + PropertyOrAccessor = 98308, + ClassMember = 106500, + } + interface Symbol { + flags: SymbolFlags; + escapedName: __String; + declarations?: Declaration[]; + valueDeclaration?: Declaration; + members?: SymbolTable; + exports?: SymbolTable; + globalExports?: SymbolTable; + } + enum InternalSymbolName { + Call = "__call", + Constructor = "__constructor", + New = "__new", + Index = "__index", + ExportStar = "__export", + Global = "__global", + Missing = "__missing", + Type = "__type", + Object = "__object", + JSXAttributes = "__jsxAttributes", + Class = "__class", + Function = "__function", + Computed = "__computed", + Resolving = "__resolving__", + ExportEquals = "export=", + Default = "default", + } + /** + * This represents a string whose leading underscore have been escaped by adding extra leading underscores. + * The shape of this brand is rather unique compared to others we've used. + * Instead of just an intersection of a string and an object, it is that union-ed + * with an intersection of void and an object. This makes it wholly incompatible + * with a normal string (which is good, it cannot be misused on assignment or on usage), + * while still being comparable with a normal string via === (also good) and castable from a string. + */ + type __String = (string & { + __escapedIdentifier: void; + }) | (void & { + __escapedIdentifier: void; + }) | InternalSymbolName; + /** ReadonlyMap where keys are `__String`s. */ + interface ReadonlyUnderscoreEscapedMap { + get(key: __String): T | undefined; + has(key: __String): boolean; + forEach(action: (value: T, key: __String) => void): void; + readonly size: number; + keys(): Iterator<__String>; + values(): Iterator; + entries(): Iterator<[__String, T]>; + } + /** Map where keys are `__String`s. */ + interface UnderscoreEscapedMap extends ReadonlyUnderscoreEscapedMap { + set(key: __String, value: T): this; + delete(key: __String): boolean; + clear(): void; + } + /** SymbolTable based on ES6 Map interface. */ + type SymbolTable = UnderscoreEscapedMap; + enum TypeFlags { + Any = 1, + String = 2, + Number = 4, + Boolean = 8, + Enum = 16, + StringLiteral = 32, + NumberLiteral = 64, + BooleanLiteral = 128, + EnumLiteral = 256, + ESSymbol = 512, + Void = 1024, + Undefined = 2048, + Null = 4096, + Never = 8192, + TypeParameter = 16384, + Object = 32768, + Union = 65536, + Intersection = 131072, + Index = 262144, + IndexedAccess = 524288, + NonPrimitive = 16777216, + MarkerType = 67108864, + Literal = 224, + Unit = 6368, + StringOrNumberLiteral = 96, + PossiblyFalsy = 7406, + StringLike = 262178, + NumberLike = 84, + BooleanLike = 136, + EnumLike = 272, + UnionOrIntersection = 196608, + StructuredType = 229376, + StructuredOrTypeVariable = 1032192, + TypeVariable = 540672, + Narrowable = 17810175, + NotUnionOrUnit = 16810497, + } + type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; + interface Type { + flags: TypeFlags; + symbol?: Symbol; + pattern?: DestructuringPattern; + aliasSymbol?: Symbol; + aliasTypeArguments?: Type[]; + } + interface LiteralType extends Type { + value: string | number; + freshType?: LiteralType; + regularType?: LiteralType; + } + interface StringLiteralType extends LiteralType { + value: string; + } + interface NumberLiteralType extends LiteralType { + value: number; + } + interface EnumType extends Type { + } + enum ObjectFlags { + Class = 1, + Interface = 2, + Reference = 4, + Tuple = 8, + Anonymous = 16, + Mapped = 32, + Instantiated = 64, + ObjectLiteral = 128, + EvolvingArray = 256, + ObjectLiteralPatternWithComputedProperties = 512, + ClassOrInterface = 3, + } + interface ObjectType extends Type { + objectFlags: ObjectFlags; + } + /** Class and interface types (ObjectFlags.Class and ObjectFlags.Interface). */ + interface InterfaceType extends ObjectType { + typeParameters: TypeParameter[]; + outerTypeParameters: TypeParameter[]; + localTypeParameters: TypeParameter[]; + thisType: TypeParameter; + } + type BaseType = ObjectType | IntersectionType; + interface InterfaceTypeWithDeclaredMembers extends InterfaceType { + declaredProperties: Symbol[]; + declaredCallSignatures: Signature[]; + declaredConstructSignatures: Signature[]; + declaredStringIndexInfo: IndexInfo; + declaredNumberIndexInfo: IndexInfo; + } + /** + * Type references (ObjectFlags.Reference). When a class or interface has type parameters or + * a "this" type, references to the class or interface are made using type references. The + * typeArguments property specifies the types to substitute for the type parameters of the + * class or interface and optionally includes an extra element that specifies the type to + * substitute for "this" in the resulting instantiation. When no extra argument is present, + * the type reference itself is substituted for "this". The typeArguments property is undefined + * if the class or interface has no type parameters and the reference isn't specifying an + * explicit "this" argument. + */ + interface TypeReference extends ObjectType { + target: GenericType; + typeArguments?: Type[]; + } + interface GenericType extends InterfaceType, TypeReference { + } + interface UnionOrIntersectionType extends Type { + types: Type[]; + } + interface UnionType extends UnionOrIntersectionType { + } + interface IntersectionType extends UnionOrIntersectionType { + } + type StructuredType = ObjectType | UnionType | IntersectionType; + interface EvolvingArrayType extends ObjectType { + elementType: Type; + finalArrayType?: Type; + } + interface TypeVariable extends Type { + } + interface TypeParameter extends TypeVariable { + /** Retrieve using getConstraintFromTypeParameter */ + constraint: Type; + default?: Type; + } + interface IndexedAccessType extends TypeVariable { + objectType: Type; + indexType: Type; + constraint?: Type; + } + interface IndexType extends Type { + type: TypeVariable | UnionOrIntersectionType; + } + enum SignatureKind { + Call = 0, + Construct = 1, + } + interface Signature { + declaration: SignatureDeclaration; + typeParameters?: TypeParameter[]; + parameters: Symbol[]; + } + enum IndexKind { + String = 0, + Number = 1, + } + interface IndexInfo { + type: Type; + isReadonly: boolean; + declaration?: SignatureDeclaration; + } + enum InferencePriority { + Contravariant = 1, + NakedTypeVariable = 2, + MappedType = 4, + ReturnType = 8, + } + interface InferenceInfo { + typeParameter: TypeParameter; + candidates: Type[]; + inferredType: Type; + priority: InferencePriority; + topLevel: boolean; + isFixed: boolean; + } + enum InferenceFlags { + InferUnionTypes = 1, + NoDefault = 2, + AnyDefault = 4, + } + /** + * Ternary values are defined such that + * x & y is False if either x or y is False. + * x & y is Maybe if either x or y is Maybe, but neither x or y is False. + * x & y is True if both x and y are True. + * x | y is False if both x and y are False. + * x | y is Maybe if either x or y is Maybe, but neither x or y is True. + * x | y is True if either x or y is True. + */ + enum Ternary { + False = 0, + Maybe = 1, + True = -1, + } + type TypeComparer = (s: Type, t: Type, reportErrors?: boolean) => Ternary; + interface JsFileExtensionInfo { + extension: string; + isMixedContent: boolean; + } + interface DiagnosticMessage { + key: string; + category: DiagnosticCategory; + code: number; + message: string; + } + /** + * A linked list of formatted diagnostic messages to be used as part of a multiline message. + * It is built from the bottom up, leaving the head to be the "main" diagnostic. + * While it seems that DiagnosticMessageChain is structurally similar to DiagnosticMessage, + * the difference is that messages are all preformatted in DMC. + */ + interface DiagnosticMessageChain { + messageText: string; + category: DiagnosticCategory; + code: number; + next?: DiagnosticMessageChain; + } + interface Diagnostic { + file: SourceFile | undefined; + start: number | undefined; + length: number | undefined; + messageText: string | DiagnosticMessageChain; + category: DiagnosticCategory; + code: number; + source?: string; + } + enum DiagnosticCategory { + Warning = 0, + Error = 1, + Message = 2, + } + enum ModuleResolutionKind { + Classic = 1, + NodeJs = 2, + } + interface PluginImport { + name: string; + } + type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | PluginImport[] | null | undefined; + interface CompilerOptions { + allowJs?: boolean; + allowSyntheticDefaultImports?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + alwaysStrict?: boolean; + baseUrl?: string; + charset?: string; + checkJs?: boolean; + declaration?: boolean; + declarationDir?: string; + disableSizeLimit?: boolean; + downlevelIteration?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + importHelpers?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + isolatedModules?: boolean; + jsx?: JsxEmit; + lib?: string[]; + locale?: string; + mapRoot?: string; + maxNodeModuleJsDepth?: number; + module?: ModuleKind; + moduleResolution?: ModuleResolutionKind; + newLine?: NewLineKind; + noEmit?: boolean; + noEmitHelpers?: boolean; + noEmitOnError?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitReturns?: boolean; + noImplicitThis?: boolean; + noStrictGenericChecks?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noImplicitUseStrict?: boolean; + noLib?: boolean; + noResolve?: boolean; + out?: string; + outDir?: string; + outFile?: string; + paths?: MapLike; + preserveConstEnums?: boolean; + preserveSymlinks?: boolean; + project?: string; + reactNamespace?: string; + jsxFactory?: string; + removeComments?: boolean; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + strict?: boolean; + strictFunctionTypes?: boolean; + strictNullChecks?: boolean; + suppressExcessPropertyErrors?: boolean; + suppressImplicitAnyIndexErrors?: boolean; + target?: ScriptTarget; + traceResolution?: boolean; + types?: string[]; + /** Paths used to compute primary types search locations */ + typeRoots?: string[]; + [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; + } + interface TypeAcquisition { + enableAutoDiscovery?: boolean; + enable?: boolean; + include?: string[]; + exclude?: string[]; + [option: string]: string[] | boolean | undefined; + } + interface DiscoverTypingsInfo { + fileNames: string[]; + projectRootPath: string; + safeListPath: string; + packageNameToTypingLocation: Map; + typeAcquisition: TypeAcquisition; + compilerOptions: CompilerOptions; + unresolvedImports: ReadonlyArray; + } + enum ModuleKind { + None = 0, + CommonJS = 1, + AMD = 2, + UMD = 3, + System = 4, + ES2015 = 5, + ESNext = 6, + } + enum JsxEmit { + None = 0, + Preserve = 1, + React = 2, + ReactNative = 3, + } + enum NewLineKind { + CarriageReturnLineFeed = 0, + LineFeed = 1, + } + interface LineAndCharacter { + line: number; + character: number; + } + enum ScriptKind { + Unknown = 0, + JS = 1, + JSX = 2, + TS = 3, + TSX = 4, + External = 5, + JSON = 6, + } + enum ScriptTarget { + ES3 = 0, + ES5 = 1, + ES2015 = 2, + ES2016 = 3, + ES2017 = 4, + ESNext = 5, + Latest = 5, + } + enum LanguageVariant { + Standard = 0, + JSX = 1, + } + /** Either a parsed command line or a parsed tsconfig.json */ + interface ParsedCommandLine { + options: CompilerOptions; + typeAcquisition?: TypeAcquisition; + fileNames: string[]; + raw?: any; + errors: Diagnostic[]; + wildcardDirectories?: MapLike; + compileOnSave?: boolean; + } + enum WatchDirectoryFlags { + None = 0, + Recursive = 1, + } + interface ExpandResult { + fileNames: string[]; + wildcardDirectories: MapLike; + } + interface ModuleResolutionHost { + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + trace?(s: string): void; + directoryExists?(directoryName: string): boolean; + /** + * Resolve a symbolic link. + * @see https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options + */ + realpath?(path: string): string; + getCurrentDirectory?(): string; + getDirectories?(path: string): string[]; + } + /** + * Represents the result of module resolution. + * Module resolution will pick up tsx/jsx/js files even if '--jsx' and '--allowJs' are turned off. + * The Program will then filter results based on these flags. + * + * Prefer to return a `ResolvedModuleFull` so that the file type does not have to be inferred. + */ + interface ResolvedModule { + /** Path of the file the module was resolved to. */ + resolvedFileName: string; + /** True if `resolvedFileName` comes from `node_modules`. */ + isExternalLibraryImport?: boolean; + } + /** + * ResolvedModule with an explicitly provided `extension` property. + * Prefer this over `ResolvedModule`. + * If changing this, remember to change `moduleResolutionIsEqualTo`. + */ + interface ResolvedModuleFull extends ResolvedModule { + /** + * Extension of resolvedFileName. This must match what's at the end of resolvedFileName. + * This is optional for backwards-compatibility, but will be added if not provided. + */ + extension: Extension; + packageId?: PackageId; + } + /** + * Unique identifier with a package name and version. + * If changing this, remember to change `packageIdIsEqual`. + */ + interface PackageId { + /** + * Name of the package. + * Should not include `@types`. + * If accessing a non-index file, this should include its name e.g. "foo/bar". + */ + name: string; + /** + * Name of a submodule within this package. + * May be "". + */ + subModuleName: string; + /** Version of the package, e.g. "1.2.3" */ + version: string; + } + enum Extension { + Ts = ".ts", + Tsx = ".tsx", + Dts = ".d.ts", + Js = ".js", + Jsx = ".jsx", + } + interface ResolvedModuleWithFailedLookupLocations { + resolvedModule: ResolvedModuleFull | undefined; + } + interface ResolvedTypeReferenceDirective { + primary: boolean; + resolvedFileName?: string; + packageId?: PackageId; + } + interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; + failedLookupLocations: string[]; + } + interface CompilerHost extends ModuleResolutionHost { + getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; + getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; + getCancellationToken?(): CancellationToken; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; + writeFile: WriteFileCallback; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + getCanonicalFileName(fileName: string): string; + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + /** + * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files + */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + getEnvironmentVariable?(name: string): string; + } + interface SourceMapRange extends TextRange { + source?: SourceMapSource; + } + interface SourceMapSource { + fileName: string; + text: string; + skipTrivia?: (pos: number) => number; + } + enum EmitFlags { + SingleLine = 1, + AdviseOnEmitNode = 2, + NoSubstitution = 4, + CapturesThis = 8, + NoLeadingSourceMap = 16, + NoTrailingSourceMap = 32, + NoSourceMap = 48, + NoNestedSourceMaps = 64, + NoTokenLeadingSourceMaps = 128, + NoTokenTrailingSourceMaps = 256, + NoTokenSourceMaps = 384, + NoLeadingComments = 512, + NoTrailingComments = 1024, + NoComments = 1536, + NoNestedComments = 2048, + HelperName = 4096, + ExportName = 8192, + LocalName = 16384, + InternalName = 32768, + Indented = 65536, + NoIndentation = 131072, + AsyncFunctionBody = 262144, + ReuseTempVariableScope = 524288, + CustomPrologue = 1048576, + NoHoisting = 2097152, + HasEndOfDeclarationMarker = 4194304, + Iterator = 8388608, + NoAsciiEscaping = 16777216, + } + interface EmitHelper { + readonly name: string; + readonly scoped: boolean; + readonly text: string; + readonly priority?: number; + } + enum EmitHint { + SourceFile = 0, + Expression = 1, + IdentifierName = 2, + MappedTypeParameter = 3, + Unspecified = 4, + } + interface TransformationContext { + /** Gets the compiler options supplied to the transformer. */ + getCompilerOptions(): CompilerOptions; + /** Starts a new lexical environment. */ + startLexicalEnvironment(): void; + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + suspendLexicalEnvironment(): void; + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + resumeLexicalEnvironment(): void; + /** Ends a lexical environment, returning any declarations. */ + endLexicalEnvironment(): Statement[]; + /** Hoists a function declaration to the containing scope. */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + /** Hoists a variable declaration to the containing scope. */ + hoistVariableDeclaration(node: Identifier): void; + /** Records a request for a non-scoped emit helper in the current context. */ + requestEmitHelper(helper: EmitHelper): void; + /** Gets and resets the requested non-scoped emit helpers. */ + readEmitHelpers(): EmitHelper[] | undefined; + /** Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ + enableSubstitution(kind: SyntaxKind): void; + /** Determines whether expression substitutions are enabled for the provided node. */ + isSubstitutionEnabled(node: Node): boolean; + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `NodeTransformer` callback. + */ + onSubstituteNode: (hint: EmitHint, node: Node) => Node; + /** + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + /** + * Hook used to allow transformers to capture state before or after + * the printer emits a node. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `NodeTransformer` callback. + */ + onEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; + } + interface TransformationResult { + /** Gets the transformed source files. */ + transformed: T[]; + /** Gets diagnostics for the transformation. */ + diagnostics?: Diagnostic[]; + /** + * Gets a substitute for a node, if one is available; otherwise, returns the original node. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to substitute. + */ + substituteNode(hint: EmitHint, node: Node): Node; + /** + * Emits a node with possible notification. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback A callback used to emit the node. + */ + emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + /** + * Clean up EmitNode entries on any parse-tree nodes. + */ + dispose(): void; + } + /** + * A function that is used to initialize and return a `Transformer` callback, which in turn + * will be used to transform one or more nodes. + */ + type TransformerFactory = (context: TransformationContext) => Transformer; + /** + * A function that transforms a node. + */ + type Transformer = (node: T) => T; + /** + * A function that accepts and possibly transforms a node. + */ + type Visitor = (node: Node) => VisitResult; + type VisitResult = T | T[] | undefined; + interface Printer { + /** + * Print a node and its subtree as-is, without any emit transformations. + * @param hint A value indicating the purpose of a node. This is primarily used to + * distinguish between an `Identifier` used in an expression position, versus an + * `Identifier` used as an `IdentifierName` as part of a declaration. For most nodes you + * should just pass `Unspecified`. + * @param node The node to print. The node and its subtree are printed as-is, without any + * emit transformations. + * @param sourceFile A source file that provides context for the node. The source text of + * the file is used to emit the original source content for literals and identifiers, while + * the identifiers of the source file are used when generating unique names to avoid + * collisions. + */ + printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string; + /** + * Prints a source file as-is, without any emit transformations. + */ + printFile(sourceFile: SourceFile): string; + /** + * Prints a bundle of source files as-is, without any emit transformations. + */ + printBundle(bundle: Bundle): string; + } + interface PrintHandlers { + /** + * A hook used by the Printer when generating unique names to avoid collisions with + * globally defined names that exist outside of the current source file. + */ + hasGlobalName?(name: string): boolean; + /** + * A hook used by the Printer to provide notifications prior to emitting a node. A + * compatible implementation **must** invoke `emitCallback` with the provided `hint` and + * `node` values. + * @param hint A hint indicating the intended purpose of the node. + * @param node The node to emit. + * @param emitCallback A callback that, when invoked, will emit the node. + * @example + * ```ts + * var printer = createPrinter(printerOptions, { + * onEmitNode(hint, node, emitCallback) { + * // set up or track state prior to emitting the node... + * emitCallback(hint, node); + * // restore state after emitting the node... + * } + * }); + * ``` + */ + onEmitNode?(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + /** + * A hook used by the Printer to perform just-in-time substitution of a node. This is + * primarily used by node transformations that need to substitute one node for another, + * such as replacing `myExportedVar` with `exports.myExportedVar`. + * @param hint A hint indicating the intended purpose of the node. + * @param node The node to emit. + * @example + * ```ts + * var printer = createPrinter(printerOptions, { + * substituteNode(hint, node) { + * // perform substitution if necessary... + * return node; + * } + * }); + * ``` + */ + substituteNode?(hint: EmitHint, node: Node): Node; + } + interface PrinterOptions { + removeComments?: boolean; + newLine?: NewLineKind; + } + interface TextSpan { + start: number; + length: number; + } + interface TextChangeRange { + span: TextSpan; + newLength: number; + } + interface SyntaxList extends Node { + _children: Node[]; + } +} +declare namespace ts { + const versionMajorMinor = "2.6"; + /** The version of the TypeScript compiler release */ + const version: string; +} +declare namespace ts { + function isExternalModuleNameRelative(moduleName: string): boolean; +} +declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; +declare function clearTimeout(handle: any): void; +declare namespace ts { + enum FileWatcherEventKind { + Created = 0, + Changed = 1, + Deleted = 2, + } + type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; + type DirectoryWatcherCallback = (fileName: string) => void; + interface WatchedFile { + fileName: string; + callback: FileWatcherCallback; + mtime?: Date; + } + interface System { + args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; + getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + /** + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching + */ + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + getModifiedTime?(path: string): Date; + /** + * This should be cryptographically secure. + * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) + */ + createHash?(data: string): string; + getMemoryUsage?(): number; + exit(exitCode?: number): void; + realpath?(path: string): string; + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout?(timeoutId: any): void; + } + interface FileWatcher { + close(): void; + } + interface DirectoryWatcher extends FileWatcher { + directoryName: string; + referenceCount: number; + } + function getNodeMajorVersion(): number; + let sys: System; +} +declare namespace ts { + function getDefaultLibFileName(options: CompilerOptions): string; + function textSpanEnd(span: TextSpan): number; + function textSpanIsEmpty(span: TextSpan): boolean; + function textSpanContainsPosition(span: TextSpan, position: number): boolean; + function textSpanContainsTextSpan(span: TextSpan, other: TextSpan): boolean; + function textSpanOverlapsWith(span: TextSpan, other: TextSpan): boolean; + function textSpanOverlap(span1: TextSpan, span2: TextSpan): TextSpan; + function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan): boolean; + function textSpanIntersectsWith(span: TextSpan, start: number, length: number): boolean; + function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number): boolean; + function textSpanIntersectsWithPosition(span: TextSpan, position: number): boolean; + function textSpanIntersection(span1: TextSpan, span2: TextSpan): TextSpan; + function createTextSpan(start: number, length: number): TextSpan; + function createTextSpanFromBounds(start: number, end: number): TextSpan; + function textChangeRangeNewSpan(range: TextChangeRange): TextSpan; + function textChangeRangeIsUnchanged(range: TextChangeRange): boolean; + function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange; + let unchangedTextChangeRange: TextChangeRange; + /** + * Called to merge all the changes that occurred across several versions of a script snapshot + * into a single change. i.e. if a user keeps making successive edits to a script we will + * have a text change from V1 to V2, V2 to V3, ..., Vn. + * + * This function will then merge those changes into a single change range valid between V1 and + * Vn. + */ + function collapseTextChangeRangesAcrossMultipleVersions(changes: ReadonlyArray): TextChangeRange; + function getTypeParameterOwner(d: Declaration): Declaration; + function isParameterPropertyDeclaration(node: Node): boolean; + function isEmptyBindingPattern(node: BindingName): node is BindingPattern; + function isEmptyBindingElement(node: BindingElement): boolean; + function getCombinedModifierFlags(node: Node): ModifierFlags; + function getCombinedNodeFlags(node: Node): NodeFlags; + /** + * Checks to see if the locale is in the appropriate format, + * and if it is, attempts to set the appropriate language. + */ + function validateLocaleAndSetLanguage(locale: string, sys: { + getExecutingFilePath(): string; + resolvePath(path: string): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + }, errors?: Push): void; + function getOriginalNode(node: Node): Node; + function getOriginalNode(node: Node, nodeTest: (node: Node) => node is T): T; + /** + * Gets a value indicating whether a node originated in the parse tree. + * + * @param node The node to test. + */ + function isParseTreeNode(node: Node): boolean; + /** + * Gets the original parse tree node for a node. + * + * @param node The original node. + * @returns The original parse tree node if found; otherwise, undefined. + */ + function getParseTreeNode(node: Node): Node; + /** + * Gets the original parse tree node for a node. + * + * @param node The original node. + * @param nodeTest A callback used to ensure the correct type of parse tree node is returned. + * @returns The original parse tree node if found; otherwise, undefined. + */ + function getParseTreeNode(node: Node, nodeTest?: (node: Node) => node is T): T; + /** + * Remove extra underscore from escaped identifier text content. + * + * @param identifier The escaped identifier text. + * @returns The unescaped identifier text. + */ + function unescapeLeadingUnderscores(identifier: __String): string; + function idText(identifier: Identifier): string; + function symbolName(symbol: Symbol): string; + /** + * Remove extra underscore from escaped identifier text content. + * @deprecated Use `id.text` for the unescaped text. + * @param identifier The escaped identifier text. + * @returns The unescaped identifier text. + */ + function unescapeIdentifier(id: string): string; + function getNameOfJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined; + function getNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined; + /** + * Gets the JSDoc parameter tags for the node if present. + * + * @remarks Returns any JSDoc param tag that matches the provided + * parameter, whether a param tag on a containing function + * expression, or a param tag on a variable declaration whose + * initializer is the containing function. The tags closest to the + * node are returned first, so in the previous example, the param + * tag on the containing function expression would be first. + * + * Does not return tags for binding patterns, because JSDoc matches + * parameters by name and binding patterns do not have a name. + */ + function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray | undefined; + /** + * Return true if the node has JSDoc parameter tags. + * + * @remarks Includes parameter tags that are not directly on the node, + * for example on a variable declaration whose initializer is a function expression. + */ + function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean; + /** Gets the JSDoc augments tag for the node if present */ + function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined; + /** Gets the JSDoc class tag for the node if present */ + function getJSDocClassTag(node: Node): JSDocClassTag | undefined; + /** Gets the JSDoc return tag for the node if present */ + function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined; + /** Gets the JSDoc template tag for the node if present */ + function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined; + /** Gets the JSDoc type tag for the node if present and valid */ + function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined; + /** + * Gets the type node for the node if provided via JSDoc. + * + * @remarks The search includes any JSDoc param tag that relates + * to the provided parameter, for example a type tag on the + * parameter itself, or a param tag on a containing function + * expression, or a param tag on a variable declaration whose + * initializer is the containing function. The tags closest to the + * node are examined first, so in the previous example, the type + * tag directly on the node would be returned. + */ + function getJSDocType(node: Node): TypeNode | undefined; + /** + * Gets the return type node for the node if provided via JSDoc's return tag. + * + * @remarks `getJSDocReturnTag` just gets the whole JSDoc tag. This function + * gets the type from inside the braces. + */ + function getJSDocReturnType(node: Node): TypeNode | undefined; + /** Get all JSDoc tags related to a node, including those on parent nodes. */ + function getJSDocTags(node: Node): ReadonlyArray | undefined; +} +declare namespace ts { + function isNumericLiteral(node: Node): node is NumericLiteral; + function isStringLiteral(node: Node): node is StringLiteral; + function isJsxText(node: Node): node is JsxText; + function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; + function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isTemplateHead(node: Node): node is TemplateHead; + function isTemplateMiddle(node: Node): node is TemplateMiddle; + function isTemplateTail(node: Node): node is TemplateTail; + function isIdentifier(node: Node): node is Identifier; + function isQualifiedName(node: Node): node is QualifiedName; + function isComputedPropertyName(node: Node): node is ComputedPropertyName; + function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration; + function isParameter(node: Node): node is ParameterDeclaration; + function isDecorator(node: Node): node is Decorator; + function isPropertySignature(node: Node): node is PropertySignature; + function isPropertyDeclaration(node: Node): node is PropertyDeclaration; + function isMethodSignature(node: Node): node is MethodSignature; + function isMethodDeclaration(node: Node): node is MethodDeclaration; + function isConstructorDeclaration(node: Node): node is ConstructorDeclaration; + function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration; + function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration; + function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration; + function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration; + function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration; + function isTypePredicateNode(node: Node): node is TypePredicateNode; + function isTypeReferenceNode(node: Node): node is TypeReferenceNode; + function isFunctionTypeNode(node: Node): node is FunctionTypeNode; + function isConstructorTypeNode(node: Node): node is ConstructorTypeNode; + function isTypeQueryNode(node: Node): node is TypeQueryNode; + function isTypeLiteralNode(node: Node): node is TypeLiteralNode; + function isArrayTypeNode(node: Node): node is ArrayTypeNode; + function isTupleTypeNode(node: Node): node is TupleTypeNode; + function isUnionTypeNode(node: Node): node is UnionTypeNode; + function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode; + function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode; + function isThisTypeNode(node: Node): node is ThisTypeNode; + function isTypeOperatorNode(node: Node): node is TypeOperatorNode; + function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode; + function isMappedTypeNode(node: Node): node is MappedTypeNode; + function isLiteralTypeNode(node: Node): node is LiteralTypeNode; + function isObjectBindingPattern(node: Node): node is ObjectBindingPattern; + function isArrayBindingPattern(node: Node): node is ArrayBindingPattern; + function isBindingElement(node: Node): node is BindingElement; + function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; + function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; + function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isCallExpression(node: Node): node is CallExpression; + function isNewExpression(node: Node): node is NewExpression; + function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; + function isTypeAssertion(node: Node): node is TypeAssertion; + function isParenthesizedExpression(node: Node): node is ParenthesizedExpression; + function skipPartiallyEmittedExpressions(node: Expression): Expression; + function skipPartiallyEmittedExpressions(node: Node): Node; + function isFunctionExpression(node: Node): node is FunctionExpression; + function isArrowFunction(node: Node): node is ArrowFunction; + function isDeleteExpression(node: Node): node is DeleteExpression; + function isTypeOfExpression(node: Node): node is TypeOfExpression; + function isVoidExpression(node: Node): node is VoidExpression; + function isAwaitExpression(node: Node): node is AwaitExpression; + function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression; + function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression; + function isBinaryExpression(node: Node): node is BinaryExpression; + function isConditionalExpression(node: Node): node is ConditionalExpression; + function isTemplateExpression(node: Node): node is TemplateExpression; + function isYieldExpression(node: Node): node is YieldExpression; + function isSpreadElement(node: Node): node is SpreadElement; + function isClassExpression(node: Node): node is ClassExpression; + function isOmittedExpression(node: Node): node is OmittedExpression; + function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments; + function isAsExpression(node: Node): node is AsExpression; + function isNonNullExpression(node: Node): node is NonNullExpression; + function isMetaProperty(node: Node): node is MetaProperty; + function isTemplateSpan(node: Node): node is TemplateSpan; + function isSemicolonClassElement(node: Node): node is SemicolonClassElement; + function isBlock(node: Node): node is Block; + function isVariableStatement(node: Node): node is VariableStatement; + function isEmptyStatement(node: Node): node is EmptyStatement; + function isExpressionStatement(node: Node): node is ExpressionStatement; + function isIfStatement(node: Node): node is IfStatement; + function isDoStatement(node: Node): node is DoStatement; + function isWhileStatement(node: Node): node is WhileStatement; + function isForStatement(node: Node): node is ForStatement; + function isForInStatement(node: Node): node is ForInStatement; + function isForOfStatement(node: Node): node is ForOfStatement; + function isContinueStatement(node: Node): node is ContinueStatement; + function isBreakStatement(node: Node): node is BreakStatement; + function isReturnStatement(node: Node): node is ReturnStatement; + function isWithStatement(node: Node): node is WithStatement; + function isSwitchStatement(node: Node): node is SwitchStatement; + function isLabeledStatement(node: Node): node is LabeledStatement; + function isThrowStatement(node: Node): node is ThrowStatement; + function isTryStatement(node: Node): node is TryStatement; + function isDebuggerStatement(node: Node): node is DebuggerStatement; + function isVariableDeclaration(node: Node): node is VariableDeclaration; + function isVariableDeclarationList(node: Node): node is VariableDeclarationList; + function isFunctionDeclaration(node: Node): node is FunctionDeclaration; + function isClassDeclaration(node: Node): node is ClassDeclaration; + function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration; + function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration; + function isEnumDeclaration(node: Node): node is EnumDeclaration; + function isModuleDeclaration(node: Node): node is ModuleDeclaration; + function isModuleBlock(node: Node): node is ModuleBlock; + function isCaseBlock(node: Node): node is CaseBlock; + function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration; + function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration; + function isImportDeclaration(node: Node): node is ImportDeclaration; + function isImportClause(node: Node): node is ImportClause; + function isNamespaceImport(node: Node): node is NamespaceImport; + function isNamedImports(node: Node): node is NamedImports; + function isImportSpecifier(node: Node): node is ImportSpecifier; + function isExportAssignment(node: Node): node is ExportAssignment; + function isExportDeclaration(node: Node): node is ExportDeclaration; + function isNamedExports(node: Node): node is NamedExports; + function isExportSpecifier(node: Node): node is ExportSpecifier; + function isMissingDeclaration(node: Node): node is MissingDeclaration; + function isExternalModuleReference(node: Node): node is ExternalModuleReference; + function isJsxElement(node: Node): node is JsxElement; + function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement; + function isJsxOpeningElement(node: Node): node is JsxOpeningElement; + function isJsxClosingElement(node: Node): node is JsxClosingElement; + function isJsxAttribute(node: Node): node is JsxAttribute; + function isJsxAttributes(node: Node): node is JsxAttributes; + function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute; + function isJsxExpression(node: Node): node is JsxExpression; + function isCaseClause(node: Node): node is CaseClause; + function isDefaultClause(node: Node): node is DefaultClause; + function isHeritageClause(node: Node): node is HeritageClause; + function isCatchClause(node: Node): node is CatchClause; + function isPropertyAssignment(node: Node): node is PropertyAssignment; + function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment; + function isSpreadAssignment(node: Node): node is SpreadAssignment; + function isEnumMember(node: Node): node is EnumMember; + function isSourceFile(node: Node): node is SourceFile; + function isBundle(node: Node): node is Bundle; + function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; + function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; + function isJSDocUnknownType(node: Node): node is JSDocUnknownType; + function isJSDocNullableType(node: Node): node is JSDocNullableType; + function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType; + function isJSDocOptionalType(node: Node): node is JSDocOptionalType; + function isJSDocFunctionType(node: Node): node is JSDocFunctionType; + function isJSDocVariadicType(node: Node): node is JSDocVariadicType; + function isJSDoc(node: Node): node is JSDoc; + function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag; + function isJSDocParameterTag(node: Node): node is JSDocParameterTag; + function isJSDocReturnTag(node: Node): node is JSDocReturnTag; + function isJSDocTypeTag(node: Node): node is JSDocTypeTag; + function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag; + function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag; + function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; + function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag; + function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; +} +declare namespace ts { + /** + * True if node is of some token syntax kind. + * For example, this is true for an IfKeyword but not for an IfStatement. + */ + function isToken(n: Node): boolean; + function isLiteralExpression(node: Node): node is LiteralExpression; + function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail; + function isStringTextContainingNode(node: Node): boolean; + function isModifier(node: Node): node is Modifier; + function isEntityName(node: Node): node is EntityName; + function isPropertyName(node: Node): node is PropertyName; + function isBindingName(node: Node): node is BindingName; + function isFunctionLike(node: Node): node is FunctionLike; + function isClassElement(node: Node): node is ClassElement; + function isClassLike(node: Node): node is ClassLikeDeclaration; + function isAccessor(node: Node): node is AccessorDeclaration; + function isTypeElement(node: Node): node is TypeElement; + function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; + /** + * Node test that determines whether a node is a valid type node. + * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* + * of a TypeNode. + */ + function isTypeNode(node: Node): node is TypeNode; + function isFunctionOrConstructorTypeNode(node: Node): node is FunctionTypeNode | ConstructorTypeNode; + function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAccessExpression | QualifiedName; + function isCallLikeExpression(node: Node): node is CallLikeExpression; + function isCallOrNewExpression(node: Node): node is CallExpression | NewExpression; + function isTemplateLiteral(node: Node): node is TemplateLiteral; + function isAssertionExpression(node: Node): node is AssertionExpression; + function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement; + function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement; + function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause; + /** True if node is of a kind that may contain comment text. */ + function isJSDocCommentContainingNode(node: Node): boolean; +} +declare namespace ts { + interface ErrorCallback { + (message: DiagnosticMessage, length: number): void; + } + interface Scanner { + getStartPos(): number; + getToken(): SyntaxKind; + getTextPos(): number; + getTokenPos(): number; + getTokenText(): string; + getTokenValue(): string; + hasExtendedUnicodeEscape(): boolean; + hasPrecedingLineBreak(): boolean; + isIdentifier(): boolean; + isReservedWord(): boolean; + isUnterminated(): boolean; + reScanGreaterToken(): SyntaxKind; + reScanSlashToken(): SyntaxKind; + reScanTemplateToken(): SyntaxKind; + scanJsxIdentifier(): SyntaxKind; + scanJsxAttributeValue(): SyntaxKind; + reScanJsxToken(): SyntaxKind; + scanJsxToken(): SyntaxKind; + scanJSDocToken(): SyntaxKind; + scan(): SyntaxKind; + getText(): string; + setText(text: string, start?: number, length?: number): void; + setOnError(onError: ErrorCallback): void; + setScriptTarget(scriptTarget: ScriptTarget): void; + setLanguageVariant(variant: LanguageVariant): void; + setTextPos(textPos: number): void; + lookAhead(callback: () => T): T; + scanRange(start: number, length: number, callback: () => T): T; + tryScan(callback: () => T): T; + } + function tokenToString(t: SyntaxKind): string | undefined; + function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number; + function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter; + function isWhiteSpaceLike(ch: number): boolean; + /** Does not include line breaks. For that, see isWhiteSpaceLike. */ + function isWhiteSpaceSingleLine(ch: number): boolean; + function isLineBreak(ch: number): boolean; + function couldStartTrivia(text: string, pos: number): boolean; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; + function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; + function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined; + function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined; + /** Optionally, get the shebang */ + function getShebang(text: string): string | undefined; + function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean; + function isIdentifierPart(ch: number, languageVersion: ScriptTarget): boolean; + function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, languageVariant?: LanguageVariant, text?: string, onError?: ErrorCallback, start?: number, length?: number): Scanner; +} +declare namespace ts { + function createNode(kind: SyntaxKind, pos?: number, end?: number): Node; + /** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, + * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns + * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks `forEachChild` must visit the children of a node in the order + * that they appear in the source code. The language service depends on this property to locate nodes by position. + */ + function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined; + function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; + function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName; + /** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ + function parseJsonText(fileName: string, sourceText: string): JsonSourceFile; + function isExternalModule(file: SourceFile): boolean; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +} +declare namespace ts { + function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { + config?: any; + error?: Diagnostic; + }; + /** + * Parse the text of the tsconfig.json file + * @param fileName The path to the config file + * @param jsonText The text of the config file + */ + function parseConfigFileTextToJson(fileName: string, jsonText: string): { + config?: any; + error?: Diagnostic; + }; + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile; + /** + * Convert the json syntax tree into the json value + */ + function convertToObject(sourceFile: JsonSourceFile, errors: Push): any; + /** + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ + function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray): ParsedCommandLine; + /** + * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ + function parseJsonSourceFileConfigFileContent(sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray): ParsedCommandLine; + function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: CompilerOptions; + errors: Diagnostic[]; + }; + function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: TypeAcquisition; + errors: Diagnostic[]; + }; +} +declare namespace ts { + function getEffectiveTypeRoots(options: CompilerOptions, host: { + directoryExists?: (directoryName: string) => boolean; + getCurrentDirectory?: () => string; + }): string[] | undefined; + /** + * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. + * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups + * is assumed to be the same as root directory of the project. + */ + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; + /** + * Given a set of options, returns the set of type directive names + * that should be included for this program automatically. + * This list could either come from the config file, + * or from enumerating the types root + initial secondary types lookup location. + * More type directives might appear in the program later as a result of loading actual source files; + * this list is only the set of defaults that are implicitly included. + */ + function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[]; + /** + * Cached module resolutions per containing directory. + * This assumes that any module id will have the same resolution for sibling files located in the same folder. + */ + interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { + getOrCreateCacheForDirectory(directoryName: string): Map; + } + /** + * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory + * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. + */ + interface NonRelativeModuleNameResolutionCache { + getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache; + } + interface PerModuleNameCache { + get(directory: string): ResolvedModuleWithFailedLookupLocations; + set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; + } + function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache; + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; + function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; + function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations; +} +declare namespace ts { + function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; + function createLiteral(value: string): StringLiteral; + function createLiteral(value: number): NumericLiteral; + function createLiteral(value: boolean): BooleanLiteral; + /** Create a string literal whose source text is read from a source node during emit. */ + function createLiteral(sourceNode: StringLiteral | NumericLiteral | Identifier): StringLiteral; + function createLiteral(value: string | number | boolean): PrimaryExpression; + function createNumericLiteral(value: string): NumericLiteral; + function createIdentifier(text: string): Identifier; + function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; + /** Create a unique temporary variable. */ + function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; + /** Create a unique temporary variable for use in a loop. */ + function createLoopVariable(): Identifier; + /** Create a unique name based on the supplied text. */ + function createUniqueName(text: string): Identifier; + /** Create a unique name generated for a node. */ + function getGeneratedNameForNode(node: Node): Identifier; + function createToken(token: TKind): Token; + function createSuper(): SuperExpression; + function createThis(): ThisExpression & Token; + function createNull(): NullLiteral & Token; + function createTrue(): BooleanLiteral & Token; + function createFalse(): BooleanLiteral & Token; + function createQualifiedName(left: EntityName, right: string | Identifier): QualifiedName; + function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; + function createComputedPropertyName(expression: Expression): ComputedPropertyName; + function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; + function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration; + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; + function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; + function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; + function createDecorator(expression: Expression): Decorator; + function updateDecorator(node: Decorator, expression: Expression): Decorator; + function createPropertySignature(modifiers: ReadonlyArray | undefined, name: PropertyName | string, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature; + function updatePropertySignature(node: PropertySignature, modifiers: ReadonlyArray | undefined, name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature; + function createProperty(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertyDeclaration; + function updateProperty(node: PropertyDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertyDeclaration; + function createMethodSignature(typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): MethodSignature; + function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined): MethodSignature; + function createMethod(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): MethodDeclaration; + function updateMethod(node: MethodDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, questionToken: QuestionToken | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): MethodDeclaration; + function createConstructor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, body: Block | undefined): ConstructorDeclaration; + function updateConstructor(node: ConstructorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, body: Block | undefined): ConstructorDeclaration; + function createGetAccessor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): GetAccessorDeclaration; + function updateGetAccessor(node: GetAccessorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: PropertyName, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): GetAccessorDeclaration; + function createSetAccessor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, parameters: ReadonlyArray, body: Block | undefined): SetAccessorDeclaration; + function updateSetAccessor(node: SetAccessorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: PropertyName, parameters: ReadonlyArray, body: Block | undefined): SetAccessorDeclaration; + function createCallSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): CallSignatureDeclaration; + function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): CallSignatureDeclaration; + function createConstructSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructSignatureDeclaration; + function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructSignatureDeclaration; + function createIndexSignature(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode): IndexSignatureDeclaration; + function updateIndexSignature(node: IndexSignatureDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode): IndexSignatureDeclaration; + function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode; + function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode): TypePredicateNode; + function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode): TypePredicateNode; + function createTypeReferenceNode(typeName: string | EntityName, typeArguments: ReadonlyArray | undefined): TypeReferenceNode; + function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined): TypeReferenceNode; + function createFunctionTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): FunctionTypeNode; + function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): FunctionTypeNode; + function createConstructorTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructorTypeNode; + function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructorTypeNode; + function createTypeQueryNode(exprName: EntityName): TypeQueryNode; + function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode; + function createTypeLiteralNode(members: ReadonlyArray): TypeLiteralNode; + function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray): TypeLiteralNode; + function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode; + function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode; + function createTupleTypeNode(elementTypes: ReadonlyArray): TupleTypeNode; + function updateTypleTypeNode(node: TupleTypeNode, elementTypes: ReadonlyArray): TupleTypeNode; + function createUnionTypeNode(types: TypeNode[]): UnionTypeNode; + function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray): UnionTypeNode; + function createIntersectionTypeNode(types: TypeNode[]): IntersectionTypeNode; + function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray): IntersectionTypeNode; + function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: ReadonlyArray): UnionOrIntersectionTypeNode; + function createParenthesizedType(type: TypeNode): ParenthesizedTypeNode; + function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode): ParenthesizedTypeNode; + function createThisTypeNode(): ThisTypeNode; + function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; + function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; + function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; + function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; + function createMappedTypeNode(readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode; + function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode; + function createLiteralTypeNode(literal: LiteralTypeNode["literal"]): LiteralTypeNode; + function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]): LiteralTypeNode; + function createObjectBindingPattern(elements: ReadonlyArray): ObjectBindingPattern; + function updateObjectBindingPattern(node: ObjectBindingPattern, elements: ReadonlyArray): ObjectBindingPattern; + function createArrayBindingPattern(elements: ReadonlyArray): ArrayBindingPattern; + function updateArrayBindingPattern(node: ArrayBindingPattern, elements: ReadonlyArray): ArrayBindingPattern; + function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression): BindingElement; + function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined): BindingElement; + function createArrayLiteral(elements?: ReadonlyArray, multiLine?: boolean): ArrayLiteralExpression; + function updateArrayLiteral(node: ArrayLiteralExpression, elements: ReadonlyArray): ArrayLiteralExpression; + function createObjectLiteral(properties?: ReadonlyArray, multiLine?: boolean): ObjectLiteralExpression; + function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; + function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; + function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; + function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; + function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; + function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion; + function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion; + function createParen(expression: Expression): ParenthesizedExpression; + function updateParen(node: ParenthesizedExpression, expression: Expression): ParenthesizedExpression; + function createFunctionExpression(modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block): FunctionExpression; + function updateFunctionExpression(node: FunctionExpression, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block): FunctionExpression; + function createArrowFunction(modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, equalsGreaterThanToken: EqualsGreaterThanToken | undefined, body: ConciseBody): ArrowFunction; + function updateArrowFunction(node: ArrowFunction, modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: ConciseBody): ArrowFunction; + function updateArrowFunction(node: ArrowFunction, modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, equalsGreaterThanToken: Token, body: ConciseBody): ArrowFunction; + function createDelete(expression: Expression): DeleteExpression; + function updateDelete(node: DeleteExpression, expression: Expression): DeleteExpression; + function createTypeOf(expression: Expression): TypeOfExpression; + function updateTypeOf(node: TypeOfExpression, expression: Expression): TypeOfExpression; + function createVoid(expression: Expression): VoidExpression; + function updateVoid(node: VoidExpression, expression: Expression): VoidExpression; + function createAwait(expression: Expression): AwaitExpression; + function updateAwait(node: AwaitExpression, expression: Expression): AwaitExpression; + function createPrefix(operator: PrefixUnaryOperator, operand: Expression): PrefixUnaryExpression; + function updatePrefix(node: PrefixUnaryExpression, operand: Expression): PrefixUnaryExpression; + function createPostfix(operand: Expression, operator: PostfixUnaryOperator): PostfixUnaryExpression; + function updatePostfix(node: PostfixUnaryExpression, operand: Expression): PostfixUnaryExpression; + function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression): BinaryExpression; + function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken): BinaryExpression; + function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; + function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; + function updateConditional(node: ConditionalExpression, condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; + function updateConditional(node: ConditionalExpression, condition: Expression, questionToken: Token, whenTrue: Expression, colonToken: Token, whenFalse: Expression): ConditionalExpression; + function createTemplateExpression(head: TemplateHead, templateSpans: ReadonlyArray): TemplateExpression; + function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: ReadonlyArray): TemplateExpression; + function createTemplateHead(text: string): TemplateHead; + function createTemplateMiddle(text: string): TemplateMiddle; + function createTemplateTail(text: string): TemplateTail; + function createNoSubstitutionTemplateLiteral(text: string): NoSubstitutionTemplateLiteral; + function createYield(expression?: Expression): YieldExpression; + function createYield(asteriskToken: AsteriskToken, expression: Expression): YieldExpression; + function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; + function createSpread(expression: Expression): SpreadElement; + function updateSpread(node: SpreadElement, expression: Expression): SpreadElement; + function createClassExpression(modifiers: ReadonlyArray | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassExpression; + function updateClassExpression(node: ClassExpression, modifiers: ReadonlyArray | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassExpression; + function createOmittedExpression(): OmittedExpression; + function createExpressionWithTypeArguments(typeArguments: ReadonlyArray, expression: Expression): ExpressionWithTypeArguments; + function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: ReadonlyArray, expression: Expression): ExpressionWithTypeArguments; + function createAsExpression(expression: Expression, type: TypeNode): AsExpression; + function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode): AsExpression; + function createNonNullExpression(expression: Expression): NonNullExpression; + function updateNonNullExpression(node: NonNullExpression, expression: Expression): NonNullExpression; + function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty; + function updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty; + function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; + function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; + function createSemicolonClassElement(): SemicolonClassElement; + function createBlock(statements: ReadonlyArray, multiLine?: boolean): Block; + function updateBlock(node: Block, statements: ReadonlyArray): Block; + function createVariableStatement(modifiers: ReadonlyArray | undefined, declarationList: VariableDeclarationList | ReadonlyArray): VariableStatement; + function updateVariableStatement(node: VariableStatement, modifiers: ReadonlyArray | undefined, declarationList: VariableDeclarationList): VariableStatement; + function createEmptyStatement(): EmptyStatement; + function createStatement(expression: Expression): ExpressionStatement; + function updateStatement(node: ExpressionStatement, expression: Expression): ExpressionStatement; + function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement): IfStatement; + function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined): IfStatement; + function createDo(statement: Statement, expression: Expression): DoStatement; + function updateDo(node: DoStatement, statement: Statement, expression: Expression): DoStatement; + function createWhile(expression: Expression, statement: Statement): WhileStatement; + function updateWhile(node: WhileStatement, expression: Expression, statement: Statement): WhileStatement; + function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement): ForStatement; + function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement): ForStatement; + function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement): ForInStatement; + function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement): ForInStatement; + function createForOf(awaitModifier: AwaitKeywordToken, initializer: ForInitializer, expression: Expression, statement: Statement): ForOfStatement; + function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken, initializer: ForInitializer, expression: Expression, statement: Statement): ForOfStatement; + function createContinue(label?: string | Identifier): ContinueStatement; + function updateContinue(node: ContinueStatement, label: Identifier | undefined): ContinueStatement; + function createBreak(label?: string | Identifier): BreakStatement; + function updateBreak(node: BreakStatement, label: Identifier | undefined): BreakStatement; + function createReturn(expression?: Expression): ReturnStatement; + function updateReturn(node: ReturnStatement, expression: Expression | undefined): ReturnStatement; + function createWith(expression: Expression, statement: Statement): WithStatement; + function updateWith(node: WithStatement, expression: Expression, statement: Statement): WithStatement; + function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement; + function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock): SwitchStatement; + function createLabel(label: string | Identifier, statement: Statement): LabeledStatement; + function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement): LabeledStatement; + function createThrow(expression: Expression): ThrowStatement; + function updateThrow(node: ThrowStatement, expression: Expression): ThrowStatement; + function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined): TryStatement; + function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined): TryStatement; + function createDebuggerStatement(): DebuggerStatement; + function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression): VariableDeclaration; + function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined): VariableDeclaration; + function createVariableDeclarationList(declarations: ReadonlyArray, flags?: NodeFlags): VariableDeclarationList; + function updateVariableDeclarationList(node: VariableDeclarationList, declarations: ReadonlyArray): VariableDeclarationList; + function createFunctionDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): FunctionDeclaration; + function updateFunctionDeclaration(node: FunctionDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): FunctionDeclaration; + function createClassDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassDeclaration; + function updateClassDeclaration(node: ClassDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassDeclaration; + function createInterfaceDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray | undefined, members: ReadonlyArray): InterfaceDeclaration; + function updateInterfaceDeclaration(node: InterfaceDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray | undefined, members: ReadonlyArray): InterfaceDeclaration; + function createTypeAliasDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, typeParameters: ReadonlyArray | undefined, type: TypeNode): TypeAliasDeclaration; + function updateTypeAliasDeclaration(node: TypeAliasDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, typeParameters: ReadonlyArray | undefined, type: TypeNode): TypeAliasDeclaration; + function createEnumDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, members: ReadonlyArray): EnumDeclaration; + function updateEnumDeclaration(node: EnumDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, members: ReadonlyArray): EnumDeclaration; + function createModuleDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags): ModuleDeclaration; + function updateModuleDeclaration(node: ModuleDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: ModuleName, body: ModuleBody | undefined): ModuleDeclaration; + function createModuleBlock(statements: ReadonlyArray): ModuleBlock; + function updateModuleBlock(node: ModuleBlock, statements: ReadonlyArray): ModuleBlock; + function createCaseBlock(clauses: ReadonlyArray): CaseBlock; + function updateCaseBlock(node: CaseBlock, clauses: ReadonlyArray): CaseBlock; + function createNamespaceExportDeclaration(name: string | Identifier): NamespaceExportDeclaration; + function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration; + function createImportEqualsDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; + function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; + function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier?: Expression): ImportDeclaration; + function updateImportDeclaration(node: ImportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression | undefined): ImportDeclaration; + function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; + function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; + function createNamespaceImport(name: Identifier): NamespaceImport; + function updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; + function createNamedImports(elements: ReadonlyArray): NamedImports; + function updateNamedImports(node: NamedImports, elements: ReadonlyArray): NamedImports; + function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + function createExportAssignment(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, isExportEquals: boolean, expression: Expression): ExportAssignment; + function updateExportAssignment(node: ExportAssignment, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, expression: Expression): ExportAssignment; + function createExportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression): ExportDeclaration; + function updateExportDeclaration(node: ExportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, exportClause: NamedExports | undefined, moduleSpecifier: Expression | undefined): ExportDeclaration; + function createNamedExports(elements: ReadonlyArray): NamedExports; + function updateNamedExports(node: NamedExports, elements: ReadonlyArray): NamedExports; + function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier; + function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier; + function createExternalModuleReference(expression: Expression): ExternalModuleReference; + function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference; + function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray, closingElement: JsxClosingElement): JsxElement; + function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray, closingElement: JsxClosingElement): JsxElement; + function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement; + function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement; + function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement; + function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement; + function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement; + function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement; + function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression): JsxAttribute; + function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression): JsxAttribute; + function createJsxAttributes(properties: ReadonlyArray): JsxAttributes; + function updateJsxAttributes(node: JsxAttributes, properties: ReadonlyArray): JsxAttributes; + function createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute; + function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression): JsxSpreadAttribute; + function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined): JsxExpression; + function updateJsxExpression(node: JsxExpression, expression: Expression | undefined): JsxExpression; + function createCaseClause(expression: Expression, statements: ReadonlyArray): CaseClause; + function updateCaseClause(node: CaseClause, expression: Expression, statements: ReadonlyArray): CaseClause; + function createDefaultClause(statements: ReadonlyArray): DefaultClause; + function updateDefaultClause(node: DefaultClause, statements: ReadonlyArray): DefaultClause; + function createHeritageClause(token: HeritageClause["token"], types: ReadonlyArray): HeritageClause; + function updateHeritageClause(node: HeritageClause, types: ReadonlyArray): HeritageClause; + function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block): CatchClause; + function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block): CatchClause; + function createPropertyAssignment(name: string | PropertyName, initializer: Expression): PropertyAssignment; + function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression): PropertyAssignment; + function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression): ShorthandPropertyAssignment; + function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined): ShorthandPropertyAssignment; + function createSpreadAssignment(expression: Expression): SpreadAssignment; + function updateSpreadAssignment(node: SpreadAssignment, expression: Expression): SpreadAssignment; + function createEnumMember(name: string | PropertyName, initializer?: Expression): EnumMember; + function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined): EnumMember; + function updateSourceFileNode(node: SourceFile, statements: ReadonlyArray): SourceFile; + /** + * Creates a shallow, memberwise clone of a node for mutation. + */ + function getMutableClone(node: T): T; + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + function createNotEmittedStatement(original: Node): NotEmittedStatement; + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. + */ + function createPartiallyEmittedExpression(expression: Expression, original?: Node): PartiallyEmittedExpression; + function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression): PartiallyEmittedExpression; + function createCommaList(elements: ReadonlyArray): CommaListExpression; + function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; + function createBundle(sourceFiles: ReadonlyArray): Bundle; + function updateBundle(node: Bundle, sourceFiles: ReadonlyArray): Bundle; + function createImmediatelyInvokedFunctionExpression(statements: Statement[]): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: Statement[]): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createComma(left: Expression, right: Expression): Expression; + function createLessThan(left: Expression, right: Expression): Expression; + function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; + function createAssignment(left: Expression, right: Expression): BinaryExpression; + function createStrictEquality(left: Expression, right: Expression): BinaryExpression; + function createStrictInequality(left: Expression, right: Expression): BinaryExpression; + function createAdd(left: Expression, right: Expression): BinaryExpression; + function createSubtract(left: Expression, right: Expression): BinaryExpression; + function createPostfixIncrement(operand: Expression): PostfixUnaryExpression; + function createLogicalAnd(left: Expression, right: Expression): BinaryExpression; + function createLogicalOr(left: Expression, right: Expression): BinaryExpression; + function createLogicalNot(operand: Expression): PrefixUnaryExpression; + function createVoidZero(): VoidExpression; + function createExportDefault(expression: Expression): ExportAssignment; + function createExternalModuleExport(exportName: Identifier): ExportDeclaration; + /** + * Clears any EmitNode entries from parse-tree nodes. + * @param sourceFile A source file. + */ + function disposeEmitNodes(sourceFile: SourceFile): void; + function setTextRange(range: T, location: TextRange | undefined): T; + /** + * Sets flags that control emit behavior of a node. + */ + function setEmitFlags(node: T, emitFlags: EmitFlags): T; + /** + * Gets a custom text range to use when emitting source maps. + */ + function getSourceMapRange(node: Node): SourceMapRange; + /** + * Sets a custom text range to use when emitting source maps. + */ + function setSourceMapRange(node: T, range: SourceMapRange | undefined): T; + /** + * Create an external source map source file reference + */ + function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource; + /** + * Gets the TextRange to use for source maps for a token of a node. + */ + function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined; + /** + * Sets the TextRange to use for source maps for a token of a node. + */ + function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined): T; + /** + * Gets a custom text range to use when emitting comments. + */ + function getCommentRange(node: Node): TextRange; + /** + * Sets a custom text range to use when emitting comments. + */ + function setCommentRange(node: T, range: TextRange): T; + function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined; + function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[]): T; + function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; + function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined; + function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[]): T; + function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; + /** + * Gets the constant value to emit for an expression. + */ + function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number; + /** + * Sets the constant value to emit for an expression. + */ + function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression; + /** + * Adds an EmitHelper to a node. + */ + function addEmitHelper(node: T, helper: EmitHelper): T; + /** + * Add EmitHelpers to a node. + */ + function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T; + /** + * Removes an EmitHelper from a node. + */ + function removeEmitHelper(node: Node, helper: EmitHelper): boolean; + /** + * Gets the EmitHelpers of a node. + */ + function getEmitHelpers(node: Node): EmitHelper[] | undefined; + /** + * Moves matching emit helpers from a source node to a target node. + */ + function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean): void; + function setOriginalNode(node: T, original: Node | undefined): T; +} +declare namespace ts { + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T; + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined; + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + function visitNodes(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; + /** + * Starts a new lexical environment and visits a statement list, ending the lexical environment + * and merging hoisted declarations upon completion. + */ + function visitLexicalEnvironment(statements: NodeArray, visitor: Visitor, context: TransformationContext, start?: number, ensureUseStrict?: boolean): NodeArray; + /** + * Starts a new lexical environment and visits a parameter list, suspending the lexical + * environment upon completion. + */ + function visitParameterList(nodes: NodeArray, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes): NodeArray; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: FunctionBody | undefined, visitor: Visitor, context: TransformationContext): FunctionBody | undefined; + /** + * Resumes a suspended lexical environment and visits a concise body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody; + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined; +} +declare namespace ts { + function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; +} +declare namespace ts { + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function resolveTripleslashReference(moduleName: string, containingFile: string): string; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + } + function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; + /** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @returns A 'Program' object. + */ + function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; +} +declare namespace ts { + interface Node { + getSourceFile(): SourceFile; + getChildCount(sourceFile?: SourceFile): number; + getChildAt(index: number, sourceFile?: SourceFile): Node; + getChildren(sourceFile?: SourceFile): Node[]; + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; + getFullStart(): number; + getEnd(): number; + getWidth(sourceFile?: SourceFile): number; + getFullWidth(): number; + getLeadingTriviaWidth(sourceFile?: SourceFile): number; + getFullText(sourceFile?: SourceFile): string; + getText(sourceFile?: SourceFile): string; + getFirstToken(sourceFile?: SourceFile): Node; + getLastToken(sourceFile?: SourceFile): Node; + forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; + } + interface Identifier { + readonly text: string; + } + interface Symbol { + readonly name: string; + getFlags(): SymbolFlags; + getEscapedName(): __String; + getName(): string; + getDeclarations(): Declaration[] | undefined; + getDocumentationComment(): SymbolDisplayPart[]; + getJsDocTags(): JSDocTagInfo[]; + } + interface Type { + getFlags(): TypeFlags; + getSymbol(): Symbol | undefined; + getProperties(): Symbol[]; + getProperty(propertyName: string): Symbol | undefined; + getApparentProperties(): Symbol[]; + getCallSignatures(): Signature[]; + getConstructSignatures(): Signature[]; + getStringIndexType(): Type | undefined; + getNumberIndexType(): Type | undefined; + getBaseTypes(): BaseType[] | undefined; + getNonNullableType(): Type; + } + interface Signature { + getDeclaration(): SignatureDeclaration; + getTypeParameters(): TypeParameter[] | undefined; + getParameters(): Symbol[]; + getReturnType(): Type; + getDocumentationComment(): SymbolDisplayPart[]; + getJsDocTags(): JSDocTagInfo[]; + } + interface SourceFile { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + getLineEndOfPosition(pos: number): number; + getLineStarts(): ReadonlyArray; + getPositionOfLineAndCharacter(line: number, character: number): number; + update(newText: string, textChangeRange: TextChangeRange): SourceFile; + } + interface SourceFileLike { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + interface SourceMapSource { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + /** + * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * snapshot is observably immutable. i.e. the same calls with the same parameters will return + * the same values. + */ + interface IScriptSnapshot { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; + /** Gets the length of this script snapshot. */ + getLength(): number; + /** + * Gets the TextChangeRange that describe how the text changed between this text and + * an older version. This information is used by the incremental parser to determine + * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * change range cannot be determined. However, in that case, incremental parsing will + * not happen and the entire document will be re - parsed. + */ + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; + /** Releases all resources held by this script snapshot */ + dispose?(): void; + } + namespace ScriptSnapshot { + function fromString(text: string): IScriptSnapshot; + } + interface PreProcessedFileInfo { + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + importedFiles: FileReference[]; + ambientExternalModules: string[]; + isLibFile: boolean; + } + interface HostCancellationToken { + isCancellationRequested(): boolean; + } + interface LanguageServiceHost { + getCompilationSettings(): CompilerOptions; + getNewLine?(): string; + getProjectVersion?(): string; + getScriptFileNames(): string[]; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; + getLocalizedDiagnosticMessages?(): any; + getCancellationToken?(): HostCancellationToken; + getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + log?(s: string): void; + trace?(s: string): void; + error?(s: string): void; + useCaseSensitiveFileNames?(): boolean; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + readFile?(path: string, encoding?: string): string | undefined; + fileExists?(path: string): boolean; + getTypeRootsVersion?(): number; + resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + directoryExists?(directoryName: string): boolean; + getDirectories?(directoryName: string): string[]; + /** + * Gets a set of custom transformers to use during emit. + */ + getCustomTransformers?(): CustomTransformers | undefined; + } + interface LanguageService { + cleanupSemanticCache(): void; + getSyntacticDiagnostics(fileName: string): Diagnostic[]; + getSemanticDiagnostics(fileName: string): Diagnostic[]; + getCompilerOptionsDiagnostics(): Diagnostic[]; + /** + * @deprecated Use getEncodedSyntacticClassifications instead. + */ + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + /** + * @deprecated Use getEncodedSemanticClassifications instead. + */ + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; + getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; + getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; + getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol; + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan; + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan; + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems; + getRenameInfo(fileName: string, position: number): RenameInfo; + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[]; + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; + getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[]; + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[]; + findReferences(fileName: string, position: number): ReferencedSymbol[]; + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[]; + /** @deprecated */ + getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[]; + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; + getNavigationBarItems(fileName: string): NavigationBarItem[]; + getNavigationTree(fileName: string): NavigationTree; + getOutliningSpans(fileName: string): OutliningSpan[]; + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; + getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; + getProgram(): Program; + dispose(): void; + } + interface Classifications { + spans: number[]; + endOfLineState: EndOfLineState; + } + interface ClassifiedSpan { + textSpan: TextSpan; + classificationType: ClassificationTypeNames; + } + /** + * Navigation bar interface designed for visual studio's dual-column layout. + * This does not form a proper tree. + * The navbar is returned as a list of top-level items, each of which has a list of child items. + * Child items always have an empty array for their `childItems`. + */ + interface NavigationBarItem { + text: string; + kind: ScriptElementKind; + kindModifiers: string; + spans: TextSpan[]; + childItems: NavigationBarItem[]; + indent: number; + bolded: boolean; + grayed: boolean; + } + /** + * Node in a tree of nested declarations in a file. + * The top node is always a script or module node. + */ + interface NavigationTree { + /** Name of the declaration, or a short description, e.g. "". */ + text: string; + kind: ScriptElementKind; + /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ + kindModifiers: string; + /** + * Spans of the nodes that generated this declaration. + * There will be more than one if this is the result of merging. + */ + spans: TextSpan[]; + /** Present if non-empty */ + childItems?: NavigationTree[]; + } + interface TodoCommentDescriptor { + text: string; + priority: number; + } + interface TodoComment { + descriptor: TodoCommentDescriptor; + message: string; + position: number; + } + class TextChange { + span: TextSpan; + newText: string; + } + interface FileTextChanges { + fileName: string; + textChanges: TextChange[]; + } + interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileTextChanges[]; + } + /** + * A set of one or more available refactoring actions, grouped under a parent refactoring. + */ + interface ApplicableRefactorInfo { + /** + * The programmatic name of the refactoring + */ + name: string; + /** + * A description of this refactoring category to show to the user. + * If the refactoring gets inlined (see below), this text will not be visible. + */ + description: string; + /** + * Inlineable refactorings can have their actions hoisted out to the top level + * of a context menu. Non-inlineanable refactorings should always be shown inside + * their parent grouping. + * + * If not specified, this value is assumed to be 'true' + */ + inlineable?: boolean; + actions: RefactorActionInfo[]; + } + /** + * Represents a single refactoring action - for example, the "Extract Method..." refactor might + * offer several actions, each corresponding to a surround class or closure to extract into. + */ + interface RefactorActionInfo { + /** + * The programmatic name of the refactoring action + */ + name: string; + /** + * A description of this refactoring action to show to the user. + * If the parent refactoring is inlined away, this will be the only text shown, + * so this description should make sense by itself if the parent is inlineable=true + */ + description: string; + } + /** + * A set of edits to make in response to a refactor action, plus an optional + * location where renaming should be invoked from + */ + interface RefactorEditInfo { + edits: FileTextChanges[]; + renameFilename: string | undefined; + renameLocation: number | undefined; + } + interface TextInsertion { + newText: string; + /** The position in newText the caret should point to after the insertion. */ + caretOffset: number; + } + interface DocumentSpan { + textSpan: TextSpan; + fileName: string; + } + interface RenameLocation extends DocumentSpan { + } + interface ReferenceEntry extends DocumentSpan { + isWriteAccess: boolean; + isDefinition: boolean; + isInString?: true; + } + interface ImplementationLocation extends DocumentSpan { + kind: ScriptElementKind; + displayParts: SymbolDisplayPart[]; + } + interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; + } + enum HighlightSpanKind { + none = "none", + definition = "definition", + reference = "reference", + writtenReference = "writtenReference", + } + interface HighlightSpan { + fileName?: string; + isInString?: true; + textSpan: TextSpan; + kind: HighlightSpanKind; + } + interface NavigateToItem { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + matchKind: string; + isCaseSensitive: boolean; + fileName: string; + textSpan: TextSpan; + containerName: string; + containerKind: ScriptElementKind; + } + enum IndentStyle { + None = 0, + Block = 1, + Smart = 2, + } + interface EditorOptions { + BaseIndentSize?: number; + IndentSize: number; + TabSize: number; + NewLineCharacter: string; + ConvertTabsToSpaces: boolean; + IndentStyle: IndentStyle; + } + interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle; + } + interface FormatCodeOptions extends EditorOptions { + InsertSpaceAfterCommaDelimiter: boolean; + InsertSpaceAfterSemicolonInForStatements: boolean; + InsertSpaceBeforeAndAfterBinaryOperators: boolean; + InsertSpaceAfterConstructor?: boolean; + InsertSpaceAfterKeywordsInControlFlowStatements: boolean; + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + InsertSpaceAfterTypeAssertion?: boolean; + InsertSpaceBeforeFunctionParenthesis?: boolean; + PlaceOpenBraceOnNewLineForFunctions: boolean; + PlaceOpenBraceOnNewLineForControlBlocks: boolean; + } + interface FormatCodeSettings extends EditorSettings { + insertSpaceAfterCommaDelimiter?: boolean; + insertSpaceAfterSemicolonInForStatements?: boolean; + insertSpaceBeforeAndAfterBinaryOperators?: boolean; + insertSpaceAfterConstructor?: boolean; + insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + insertSpaceAfterTypeAssertion?: boolean; + insertSpaceBeforeFunctionParenthesis?: boolean; + placeOpenBraceOnNewLineForFunctions?: boolean; + placeOpenBraceOnNewLineForControlBlocks?: boolean; + } + interface DefinitionInfo { + fileName: string; + textSpan: TextSpan; + kind: ScriptElementKind; + name: string; + containerKind: ScriptElementKind; + containerName: string; + } + interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { + displayParts: SymbolDisplayPart[]; + } + interface ReferencedSymbol { + definition: ReferencedSymbolDefinitionInfo; + references: ReferenceEntry[]; + } + enum SymbolDisplayPartKind { + aliasName = 0, + className = 1, + enumName = 2, + fieldName = 3, + interfaceName = 4, + keyword = 5, + lineBreak = 6, + numericLiteral = 7, + stringLiteral = 8, + localName = 9, + methodName = 10, + moduleName = 11, + operator = 12, + parameterName = 13, + propertyName = 14, + punctuation = 15, + space = 16, + text = 17, + typeParameterName = 18, + enumMemberName = 19, + functionName = 20, + regularExpressionLiteral = 21, + } + interface SymbolDisplayPart { + text: string; + kind: string; + } + interface JSDocTagInfo { + name: string; + text?: string; + } + interface QuickInfo { + kind: ScriptElementKind; + kindModifiers: string; + textSpan: TextSpan; + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + interface RenameInfo { + canRename: boolean; + localizedErrorMessage: string; + displayName: string; + fullDisplayName: string; + kind: ScriptElementKind; + kindModifiers: string; + triggerSpan: TextSpan; + } + interface SignatureHelpParameter { + name: string; + documentation: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; + isOptional: boolean; + } + /** + * Represents a single signature to show in signature help. + * The id is used for subsequent calls into the language service to ask questions about the + * signature help item in the context of any documents that have been updated. i.e. after + * an edit has happened, while signature help is still active, the host can ask important + * questions like 'what parameter is the user currently contained within?'. + */ + interface SignatureHelpItem { + isVariadic: boolean; + prefixDisplayParts: SymbolDisplayPart[]; + suffixDisplayParts: SymbolDisplayPart[]; + separatorDisplayParts: SymbolDisplayPart[]; + parameters: SignatureHelpParameter[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + /** + * Represents a set of signature help items, and the preferred item that should be selected. + */ + interface SignatureHelpItems { + items: SignatureHelpItem[]; + applicableSpan: TextSpan; + selectedItemIndex: number; + argumentIndex: number; + argumentCount: number; + } + interface CompletionInfo { + isGlobalCompletion: boolean; + isMemberCompletion: boolean; + /** + * true when the current location also allows for a new identifier + */ + isNewIdentifierLocation: boolean; + entries: CompletionEntry[]; + } + interface CompletionEntry { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. It will be + * set if the required span differs from the one generated by the default replacement behavior and should + * be used in that case + */ + replacementSpan?: TextSpan; + } + interface CompletionEntryDetails { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + interface OutliningSpan { + /** The span of the document to actually collapse. */ + textSpan: TextSpan; + /** The span of the document to display when the user hovers over the collapsed span. */ + hintSpan: TextSpan; + /** The text to display in the editor for the collapsed region. */ + bannerText: string; + /** + * Whether or not this region should be automatically collapsed when + * the 'Collapse to Definitions' command is invoked. + */ + autoCollapse: boolean; + } + interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + enum OutputFileType { + JavaScript = 0, + SourceMap = 1, + Declaration = 2, + } + interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } + enum EndOfLineState { + None = 0, + InMultiLineCommentTrivia = 1, + InSingleQuoteStringLiteral = 2, + InDoubleQuoteStringLiteral = 3, + InTemplateHeadOrNoSubstitutionTemplate = 4, + InTemplateMiddleOrTail = 5, + InTemplateSubstitutionPosition = 6, + } + enum TokenClass { + Punctuation = 0, + Keyword = 1, + Operator = 2, + Comment = 3, + Whitespace = 4, + Identifier = 5, + NumberLiteral = 6, + StringLiteral = 7, + RegExpLiteral = 8, + } + interface ClassificationResult { + finalLexState: EndOfLineState; + entries: ClassificationInfo[]; + } + interface ClassificationInfo { + length: number; + classification: TokenClass; + } + interface Classifier { + /** + * Gives lexical classifications of tokens on a line without any syntactic context. + * For instance, a token consisting of the text 'string' can be either an identifier + * named 'string' or the keyword 'string', however, because this classifier is not aware, + * it relies on certain heuristics to give acceptable results. For classifications where + * speed trumps accuracy, this function is preferable; however, for true accuracy, the + * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the + * lexical, syntactic, and semantic classifiers may issue the best user experience. + * + * @param text The text of a line to classify. + * @param lexState The state of the lexical classifier at the end of the previous line. + * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. + * If there is no syntactic classifier (syntacticClassifierAbsent=true), + * certain heuristics may be used in its place; however, if there is a + * syntactic classifier (syntacticClassifierAbsent=false), certain + * classifications which may be incorrectly categorized will be given + * back as Identifiers in order to allow the syntactic classifier to + * subsume the classification. + * @deprecated Use getLexicalClassifications instead. + */ + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; + getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; + } + enum ScriptElementKind { + unknown = "", + warning = "warning", + /** predefined type (void) or keyword (class) */ + keyword = "keyword", + /** top level script node */ + scriptElement = "script", + /** module foo {} */ + moduleElement = "module", + /** class X {} */ + classElement = "class", + /** var x = class X {} */ + localClassElement = "local class", + /** interface Y {} */ + interfaceElement = "interface", + /** type T = ... */ + typeElement = "type", + /** enum E */ + enumElement = "enum", + enumMemberElement = "enum member", + /** + * Inside module and script only + * const v = .. + */ + variableElement = "var", + /** Inside function */ + localVariableElement = "local var", + /** + * Inside module and script only + * function f() { } + */ + functionElement = "function", + /** Inside function */ + localFunctionElement = "local function", + /** class X { [public|private]* foo() {} } */ + memberFunctionElement = "method", + /** class X { [public|private]* [get|set] foo:number; } */ + memberGetAccessorElement = "getter", + memberSetAccessorElement = "setter", + /** + * class X { [public|private]* foo:number; } + * interface Y { foo:number; } + */ + memberVariableElement = "property", + /** class X { constructor() { } } */ + constructorImplementationElement = "constructor", + /** interface Y { ():number; } */ + callSignatureElement = "call", + /** interface Y { []:number; } */ + indexSignatureElement = "index", + /** interface Y { new():Y; } */ + constructSignatureElement = "construct", + /** function foo(*Y*: string) */ + parameterElement = "parameter", + typeParameterElement = "type parameter", + primitiveType = "primitive type", + label = "label", + alias = "alias", + constElement = "const", + letElement = "let", + directory = "directory", + externalModuleName = "external module name", + /** + * + */ + jsxAttribute = "JSX attribute", + } + enum ScriptElementKindModifier { + none = "", + publicMemberModifier = "public", + privateMemberModifier = "private", + protectedMemberModifier = "protected", + exportedModifier = "export", + ambientModifier = "declare", + staticModifier = "static", + abstractModifier = "abstract", + } + enum ClassificationTypeNames { + comment = "comment", + identifier = "identifier", + keyword = "keyword", + numericLiteral = "number", + operator = "operator", + stringLiteral = "string", + whiteSpace = "whitespace", + text = "text", + punctuation = "punctuation", + className = "class name", + enumName = "enum name", + interfaceName = "interface name", + moduleName = "module name", + typeParameterName = "type parameter name", + typeAliasName = "type alias name", + parameterName = "parameter name", + docCommentTagName = "doc comment tag name", + jsxOpenTagName = "jsx open tag name", + jsxCloseTagName = "jsx close tag name", + jsxSelfClosingTagName = "jsx self closing tag name", + jsxAttribute = "jsx attribute", + jsxText = "jsx text", + jsxAttributeStringLiteralValue = "jsx attribute string literal value", + } + enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + } +} +declare namespace ts { + function createClassifier(): Classifier; +} +declare namespace ts { + /** + * The document registry represents a store of SourceFile objects that can be shared between + * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library + * file (lib.d.ts). + * + * A more advanced use of the document registry is to serialize sourceFile objects to disk + * and re-hydrate them when needed. + * + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * to all subsequent createLanguageService calls. + */ + interface DocumentRegistry { + /** + * Request a stored SourceFile with a given fileName and compilationSettings. + * The first call to acquire will call createLanguageServiceSourceFile to generate + * the SourceFile if was not found in the registry. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @parm scriptSnapshot Text of the file. Only used if the file was not found + * in the registry and a new one was created. + * @parm version Current version of the file. Only used if the file was not found + * in the registry and a new one was created. + */ + acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + /** + * Request an updated version of an already existing SourceFile with a given fileName + * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile + * to get an updated SourceFile. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @param scriptSnapshot Text of the file. + * @param version Current version of the file. + */ + updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; + reportStats(): string; + } + type DocumentRegistryBucketKey = string & { + __bucketKey: any; + }; + function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry; +} +declare namespace ts { + function preProcessFile(sourceText: string, readImportFiles?: boolean, detectJavaScriptImports?: boolean): PreProcessedFileInfo; +} +declare namespace ts { + interface TranspileOptions { + compilerOptions?: CompilerOptions; + fileName?: string; + reportDiagnostics?: boolean; + moduleName?: string; + renamedDependencies?: MapLike; + transformers?: CustomTransformers; + } + interface TranspileOutput { + outputText: string; + diagnostics?: Diagnostic[]; + sourceMapText?: string; + } + function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput; + function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string; +} +declare namespace ts { + /** The version of the language service API */ + const servicesVersion = "0.5"; + interface DisplayPartsSymbolWriter extends SymbolWriter { + displayParts(): SymbolDisplayPart[]; + } + function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; + function displayPartsToString(displayParts: SymbolDisplayPart[]): string; + function getDefaultCompilerOptions(): CompilerOptions; + function getSupportedCodeFixes(): string[]; + function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; + let disableIncrementalParsing: boolean; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; + function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; + /** + * Get the path of the default library files (lib.d.ts) as distributed with the typescript + * node package. + * The functionality is not supported if the ts module is consumed outside of a node module. + */ + function getDefaultLibFilePath(options: CompilerOptions): string; +} +declare namespace ts.server { + interface CompressedData { + length: number; + compressionKind: string; + data: any; + } + type RequireResult = { + module: {}; + error: undefined; + } | { + module: undefined; + error: { + stack?: string; + message?: string; + }; + }; + interface ServerHost extends System { + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout(timeoutId: any): void; + setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; + clearImmediate(timeoutId: any): void; + gc?(): void; + trace?(s: string): void; + require?(initialPath: string, moduleName: string): RequireResult; + } + interface SortedArray extends Array { + " __sortedArrayBrand": any; + } + interface SortedReadonlyArray extends ReadonlyArray { + " __sortedArrayBrand": any; + } + interface TypingInstallerRequest { + readonly projectName: string; + readonly kind: "discover" | "closeProject"; + } + interface DiscoverTypings extends TypingInstallerRequest { + readonly fileNames: string[]; + readonly projectRootPath: Path; + readonly compilerOptions: CompilerOptions; + readonly typeAcquisition: TypeAcquisition; + readonly unresolvedImports: SortedReadonlyArray; + readonly cachePath?: string; + readonly kind: "discover"; + } + interface CloseProject extends TypingInstallerRequest { + readonly kind: "closeProject"; + } + type ActionSet = "action::set"; + type ActionInvalidate = "action::invalidate"; + type EventBeginInstallTypes = "event::beginInstallTypes"; + type EventEndInstallTypes = "event::endInstallTypes"; + type EventInitializationFailed = "event::initializationFailed"; + interface TypingInstallerResponse { + readonly kind: ActionSet | ActionInvalidate | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; + } + interface InitializationFailedResponse extends TypingInstallerResponse { + readonly kind: EventInitializationFailed; + readonly message: string; + } + interface ProjectResponse extends TypingInstallerResponse { + readonly projectName: string; + } + interface SetTypings extends ProjectResponse { + readonly typeAcquisition: TypeAcquisition; + readonly compilerOptions: CompilerOptions; + readonly typings: string[]; + readonly unresolvedImports: SortedReadonlyArray; + readonly kind: ActionSet; + } + interface InvalidateCachedTypings extends ProjectResponse { + readonly kind: ActionInvalidate; + } + interface InstallTypes extends ProjectResponse { + readonly kind: EventBeginInstallTypes | EventEndInstallTypes; + readonly eventId: number; + readonly typingsInstallerVersion: string; + readonly packagesToInstall: ReadonlyArray; + } + interface BeginInstallTypes extends InstallTypes { + readonly kind: EventBeginInstallTypes; + } + interface EndInstallTypes extends InstallTypes { + readonly kind: EventEndInstallTypes; + readonly installSuccess: boolean; + } +} +declare namespace ts.server { + const ActionSet: ActionSet; + const ActionInvalidate: ActionInvalidate; + const EventBeginInstallTypes: EventBeginInstallTypes; + const EventEndInstallTypes: EventEndInstallTypes; + const EventInitializationFailed: EventInitializationFailed; + namespace Arguments { + const GlobalCacheLocation = "--globalTypingsCacheLocation"; + const LogFile = "--logFile"; + const EnableTelemetry = "--enableTelemetry"; + const TypingSafeListLocation = "--typingSafeListLocation"; + const TypesMapLocation = "--typesMapLocation"; + /** + * This argument specifies the location of the NPM executable. + * typingsInstaller will run the command with `${npmLocation} install ...`. + */ + const NpmLocation = "--npmLocation"; + } + function hasArgument(argumentName: string): boolean; + function findArgument(argumentName: string): string | undefined; +} +declare namespace ts.server { + enum LogLevel { + terse = 0, + normal = 1, + requestTime = 2, + verbose = 3, + } + const emptyArray: SortedReadonlyArray; + interface Logger { + close(): void; + hasLevel(level: LogLevel): boolean; + loggingEnabled(): boolean; + perftrc(s: string): void; + info(s: string): void; + startGroup(): void; + endGroup(): void; + msg(s: string, type?: Msg.Types): void; + getLogFileName(): string; + } + namespace Msg { + type Err = "Err"; + const Err: Err; + type Info = "Info"; + const Info: Info; + type Perf = "Perf"; + const Perf: Perf; + type Types = Err | Info | Perf; + } + function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings; + namespace Errors { + function ThrowNoProject(): never; + function ThrowProjectLanguageServiceDisabled(): never; + function ThrowProjectDoesNotContainDocument(fileName: string, project: Project): never; + } + function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings; + function mergeMapLikes(target: MapLike, source: MapLike): void; + type NormalizedPath = string & { + __normalizedPathTag: any; + }; + function toNormalizedPath(fileName: string): NormalizedPath; + function normalizedPathToPath(normalizedPath: NormalizedPath, currentDirectory: string, getCanonicalFileName: (f: string) => string): Path; + function asNormalizedPath(fileName: string): NormalizedPath; + interface NormalizedPathMap { + get(path: NormalizedPath): T; + set(path: NormalizedPath, value: T): void; + contains(path: NormalizedPath): boolean; + remove(path: NormalizedPath): void; + } + function createNormalizedPathMap(): NormalizedPathMap; + interface ProjectOptions { + configHasExtendsProperty: boolean; + /** + * true if config file explicitly listed files + */ + configHasFilesProperty: boolean; + configHasIncludeProperty: boolean; + configHasExcludeProperty: boolean; + /** + * these fields can be present in the project file + */ + files?: string[]; + wildcardDirectories?: Map; + compilerOptions?: CompilerOptions; + typeAcquisition?: TypeAcquisition; + compileOnSave?: boolean; + } + function isInferredProjectName(name: string): boolean; + function makeInferredProjectName(counter: number): string; + function createSortedArray(): SortedArray; + class ThrottledOperations { + private readonly host; + private pendingTimeouts; + constructor(host: ServerHost); + /** + * Wait `number` milliseconds and then invoke `cb`. If, while waiting, schedule + * is called again with the same `operationId`, cancel this operation in favor + * of the new one. (Note that the amount of time the canceled operation had been + * waiting does not affect the amount of time that the new operation waits.) + */ + schedule(operationId: string, delay: number, cb: () => void): void; + private static run(self, operationId, cb); + } + class GcTimer { + private readonly host; + private readonly delay; + private readonly logger; + private timerId; + constructor(host: ServerHost, delay: number, logger: Logger); + scheduleCollect(): void; + private static run(self); + } +} +/** + * Declaration module describing the TypeScript Server protocol + */ +declare namespace ts.server.protocol { + enum CommandTypes { + Brace = "brace", + BraceCompletion = "braceCompletion", + GetSpanOfEnclosingComment = "getSpanOfEnclosingComment", + Change = "change", + Close = "close", + Completions = "completions", + CompletionDetails = "completionEntryDetails", + CompileOnSaveAffectedFileList = "compileOnSaveAffectedFileList", + CompileOnSaveEmitFile = "compileOnSaveEmitFile", + Configure = "configure", + Definition = "definition", + Implementation = "implementation", + Exit = "exit", + Format = "format", + Formatonkey = "formatonkey", + Geterr = "geterr", + GeterrForProject = "geterrForProject", + SemanticDiagnosticsSync = "semanticDiagnosticsSync", + SyntacticDiagnosticsSync = "syntacticDiagnosticsSync", + NavBar = "navbar", + Navto = "navto", + NavTree = "navtree", + NavTreeFull = "navtree-full", + Occurrences = "occurrences", + DocumentHighlights = "documentHighlights", + Open = "open", + Quickinfo = "quickinfo", + References = "references", + Reload = "reload", + Rename = "rename", + Saveto = "saveto", + SignatureHelp = "signatureHelp", + TypeDefinition = "typeDefinition", + ProjectInfo = "projectInfo", + ReloadProjects = "reloadProjects", + Unknown = "unknown", + OpenExternalProject = "openExternalProject", + OpenExternalProjects = "openExternalProjects", + CloseExternalProject = "closeExternalProject", + TodoComments = "todoComments", + Indentation = "indentation", + DocCommentTemplate = "docCommentTemplate", + CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", + GetCodeFixes = "getCodeFixes", + GetSupportedCodeFixes = "getSupportedCodeFixes", + GetApplicableRefactors = "getApplicableRefactors", + GetEditsForRefactor = "getEditsForRefactor", + } + /** + * A TypeScript Server message + */ + interface Message { + /** + * Sequence number of the message + */ + seq: number; + /** + * One of "request", "response", or "event" + */ + type: "request" | "response" | "event"; + } + /** + * Client-initiated request message + */ + interface Request extends Message { + /** + * The command to execute + */ + command: string; + /** + * Object containing arguments for the command + */ + arguments?: any; + } + /** + * Request to reload the project structure for all the opened files + */ + interface ReloadProjectsRequest extends Message { + command: CommandTypes.ReloadProjects; + } + /** + * Server-initiated event message + */ + interface Event extends Message { + /** + * Name of event + */ + event: string; + /** + * Event-specific information + */ + body?: any; + } + /** + * Response by server to client request message. + */ + interface Response extends Message { + /** + * Sequence number of the request message. + */ + request_seq: number; + /** + * Outcome of the request. + */ + success: boolean; + /** + * The command requested. + */ + command: string; + /** + * Contains error message if success === false. + */ + message?: string; + /** + * Contains message body if success === true. + */ + body?: any; + } + /** + * Arguments for FileRequest messages. + */ + interface FileRequestArgs { + /** + * The file for the request (absolute pathname required). + */ + file: string; + projectFileName?: string; + } + /** + * Requests a JS Doc comment template for a given position + */ + interface DocCommentTemplateRequest extends FileLocationRequest { + command: CommandTypes.DocCommentTemplate; + } + /** + * Response to DocCommentTemplateRequest + */ + interface DocCommandTemplateResponse extends Response { + body?: TextInsertion; + } + /** + * A request to get TODO comments from the file + */ + interface TodoCommentRequest extends FileRequest { + command: CommandTypes.TodoComments; + arguments: TodoCommentRequestArgs; + } + /** + * Arguments for TodoCommentRequest request. + */ + interface TodoCommentRequestArgs extends FileRequestArgs { + /** + * Array of target TodoCommentDescriptors that describes TODO comments to be found + */ + descriptors: TodoCommentDescriptor[]; + } + /** + * Response for TodoCommentRequest request. + */ + interface TodoCommentsResponse extends Response { + body?: TodoComment[]; + } + /** + * A request to determine if the caret is inside a comment. + */ + interface SpanOfEnclosingCommentRequest extends FileLocationRequest { + command: CommandTypes.GetSpanOfEnclosingComment; + arguments: SpanOfEnclosingCommentRequestArgs; + } + interface SpanOfEnclosingCommentRequestArgs extends FileLocationRequestArgs { + /** + * Requires that the enclosing span be a multi-line comment, or else the request returns undefined. + */ + onlyMultiLine: boolean; + } + /** + * A request to get indentation for a location in file + */ + interface IndentationRequest extends FileLocationRequest { + command: CommandTypes.Indentation; + arguments: IndentationRequestArgs; + } + /** + * Response for IndentationRequest request. + */ + interface IndentationResponse extends Response { + body?: IndentationResult; + } + /** + * Indentation result representing where indentation should be placed + */ + interface IndentationResult { + /** + * The base position in the document that the indent should be relative to + */ + position: number; + /** + * The number of columns the indent should be at relative to the position's column. + */ + indentation: number; + } + /** + * Arguments for IndentationRequest request. + */ + interface IndentationRequestArgs extends FileLocationRequestArgs { + /** + * An optional set of settings to be used when computing indentation. + * If argument is omitted - then it will use settings for file that were previously set via 'configure' request or global settings. + */ + options?: EditorSettings; + } + /** + * Arguments for ProjectInfoRequest request. + */ + interface ProjectInfoRequestArgs extends FileRequestArgs { + /** + * Indicate if the file name list of the project is needed + */ + needFileNameList: boolean; + } + /** + * A request to get the project information of the current file. + */ + interface ProjectInfoRequest extends Request { + command: CommandTypes.ProjectInfo; + arguments: ProjectInfoRequestArgs; + } + /** + * A request to retrieve compiler options diagnostics for a project + */ + interface CompilerOptionsDiagnosticsRequest extends Request { + arguments: CompilerOptionsDiagnosticsRequestArgs; + } + /** + * Arguments for CompilerOptionsDiagnosticsRequest request. + */ + interface CompilerOptionsDiagnosticsRequestArgs { + /** + * Name of the project to retrieve compiler options diagnostics. + */ + projectFileName: string; + } + /** + * Response message body for "projectInfo" request + */ + interface ProjectInfo { + /** + * For configured project, this is the normalized path of the 'tsconfig.json' file + * For inferred project, this is undefined + */ + configFileName: string; + /** + * The list of normalized file name in the project, including 'lib.d.ts' + */ + fileNames?: string[]; + /** + * Indicates if the project has a active language service instance + */ + languageServiceDisabled?: boolean; + } + /** + * Represents diagnostic info that includes location of diagnostic in two forms + * - start position and length of the error span + * - startLocation and endLocation - a pair of Location objects that store start/end line and offset of the error span. + */ + interface DiagnosticWithLinePosition { + message: string; + start: number; + length: number; + startLocation: Location; + endLocation: Location; + category: string; + code: number; + } + /** + * Response message for "projectInfo" request + */ + interface ProjectInfoResponse extends Response { + body?: ProjectInfo; + } + /** + * Request whose sole parameter is a file name. + */ + interface FileRequest extends Request { + arguments: FileRequestArgs; + } + /** + * Instances of this interface specify a location in a source file: + * (file, line, character offset), where line and character offset are 1-based. + */ + interface FileLocationRequestArgs extends FileRequestArgs { + /** + * The line number for the request (1-based). + */ + line: number; + /** + * The character offset (on the line) for the request (1-based). + */ + offset: number; + } + type FileLocationOrRangeRequestArgs = FileLocationRequestArgs | FileRangeRequestArgs; + /** + * Request refactorings at a given position or selection area. + */ + interface GetApplicableRefactorsRequest extends Request { + command: CommandTypes.GetApplicableRefactors; + arguments: GetApplicableRefactorsRequestArgs; + } + type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs; + /** + * Response is a list of available refactorings. + * Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring + */ + interface GetApplicableRefactorsResponse extends Response { + body?: ApplicableRefactorInfo[]; + } + /** + * A set of one or more available refactoring actions, grouped under a parent refactoring. + */ + interface ApplicableRefactorInfo { + /** + * The programmatic name of the refactoring + */ + name: string; + /** + * A description of this refactoring category to show to the user. + * If the refactoring gets inlined (see below), this text will not be visible. + */ + description: string; + /** + * Inlineable refactorings can have their actions hoisted out to the top level + * of a context menu. Non-inlineanable refactorings should always be shown inside + * their parent grouping. + * + * If not specified, this value is assumed to be 'true' + */ + inlineable?: boolean; + actions: RefactorActionInfo[]; + } + /** + * Represents a single refactoring action - for example, the "Extract Method..." refactor might + * offer several actions, each corresponding to a surround class or closure to extract into. + */ + interface RefactorActionInfo { + /** + * The programmatic name of the refactoring action + */ + name: string; + /** + * A description of this refactoring action to show to the user. + * If the parent refactoring is inlined away, this will be the only text shown, + * so this description should make sense by itself if the parent is inlineable=true + */ + description: string; + } + interface GetEditsForRefactorRequest extends Request { + command: CommandTypes.GetEditsForRefactor; + arguments: GetEditsForRefactorRequestArgs; + } + /** + * Request the edits that a particular refactoring action produces. + * Callers must specify the name of the refactor and the name of the action. + */ + type GetEditsForRefactorRequestArgs = FileLocationOrRangeRequestArgs & { + refactor: string; + action: string; + }; + interface GetEditsForRefactorResponse extends Response { + body?: RefactorEditInfo; + } + interface RefactorEditInfo { + edits: FileCodeEdits[]; + /** + * An optional location where the editor should start a rename operation once + * the refactoring edits have been applied + */ + renameLocation?: Location; + renameFilename?: string; + } + /** + * Request for the available codefixes at a specific position. + */ + interface CodeFixRequest extends Request { + command: CommandTypes.GetCodeFixes; + arguments: CodeFixRequestArgs; + } + interface FileRangeRequestArgs extends FileRequestArgs { + /** + * The line number for the request (1-based). + */ + startLine: number; + /** + * The character offset (on the line) for the request (1-based). + */ + startOffset: number; + /** + * The line number for the request (1-based). + */ + endLine: number; + /** + * The character offset (on the line) for the request (1-based). + */ + endOffset: number; + } + /** + * Instances of this interface specify errorcodes on a specific location in a sourcefile. + */ + interface CodeFixRequestArgs extends FileRangeRequestArgs { + /** + * Errorcodes we want to get the fixes for. + */ + errorCodes?: number[]; + } + /** + * Response for GetCodeFixes request. + */ + interface GetCodeFixesResponse extends Response { + body?: CodeAction[]; + } + /** + * A request whose arguments specify a file location (file, line, col). + */ + interface FileLocationRequest extends FileRequest { + arguments: FileLocationRequestArgs; + } + /** + * A request to get codes of supported code fixes. + */ + interface GetSupportedCodeFixesRequest extends Request { + command: CommandTypes.GetSupportedCodeFixes; + } + /** + * A response for GetSupportedCodeFixesRequest request. + */ + interface GetSupportedCodeFixesResponse extends Response { + /** + * List of error codes supported by the server. + */ + body?: string[]; + } + /** + * Arguments for EncodedSemanticClassificationsRequest request. + */ + interface EncodedSemanticClassificationsRequestArgs extends FileRequestArgs { + /** + * Start position of the span. + */ + start: number; + /** + * Length of the span. + */ + length: number; + } + /** + * Arguments in document highlight request; include: filesToSearch, file, + * line, offset. + */ + interface DocumentHighlightsRequestArgs extends FileLocationRequestArgs { + /** + * List of files to search for document highlights. + */ + filesToSearch: string[]; + } + /** + * Go to definition request; value of command field is + * "definition". Return response giving the file locations that + * define the symbol found in file at location line, col. + */ + interface DefinitionRequest extends FileLocationRequest { + command: CommandTypes.Definition; + } + /** + * Go to type request; value of command field is + * "typeDefinition". Return response giving the file locations that + * define the type for the symbol found in file at location line, col. + */ + interface TypeDefinitionRequest extends FileLocationRequest { + command: CommandTypes.TypeDefinition; + } + /** + * Go to implementation request; value of command field is + * "implementation". Return response giving the file locations that + * implement the symbol found in file at location line, col. + */ + interface ImplementationRequest extends FileLocationRequest { + command: CommandTypes.Implementation; + } + /** + * Location in source code expressed as (one-based) line and (one-based) column offset. + */ + interface Location { + line: number; + offset: number; + } + /** + * Object found in response messages defining a span of text in source code. + */ + interface TextSpan { + /** + * First character of the definition. + */ + start: Location; + /** + * One character past last character of the definition. + */ + end: Location; + } + /** + * Object found in response messages defining a span of text in a specific source file. + */ + interface FileSpan extends TextSpan { + /** + * File containing text span. + */ + file: string; + } + /** + * Definition response message. Gives text range for definition. + */ + interface DefinitionResponse extends Response { + body?: FileSpan[]; + } + /** + * Definition response message. Gives text range for definition. + */ + interface TypeDefinitionResponse extends Response { + body?: FileSpan[]; + } + /** + * Implementation response message. Gives text range for implementations. + */ + interface ImplementationResponse extends Response { + body?: FileSpan[]; + } + /** + * Request to get brace completion for a location in the file. + */ + interface BraceCompletionRequest extends FileLocationRequest { + command: CommandTypes.BraceCompletion; + arguments: BraceCompletionRequestArgs; + } + /** + * Argument for BraceCompletionRequest request. + */ + interface BraceCompletionRequestArgs extends FileLocationRequestArgs { + /** + * Kind of opening brace + */ + openingBrace: string; + } + /** + * Get occurrences request; value of command field is + * "occurrences". Return response giving spans that are relevant + * in the file at a given line and column. + */ + interface OccurrencesRequest extends FileLocationRequest { + command: CommandTypes.Occurrences; + } + interface OccurrencesResponseItem extends FileSpan { + /** + * True if the occurrence is a write location, false otherwise. + */ + isWriteAccess: boolean; + /** + * True if the occurrence is in a string, undefined otherwise; + */ + isInString?: true; + } + interface OccurrencesResponse extends Response { + body?: OccurrencesResponseItem[]; + } + /** + * Get document highlights request; value of command field is + * "documentHighlights". Return response giving spans that are relevant + * in the file at a given line and column. + */ + interface DocumentHighlightsRequest extends FileLocationRequest { + command: CommandTypes.DocumentHighlights; + arguments: DocumentHighlightsRequestArgs; + } + /** + * Span augmented with extra information that denotes the kind of the highlighting to be used for span. + */ + interface HighlightSpan extends TextSpan { + kind: HighlightSpanKind; + } + /** + * Represents a set of highligh spans for a give name + */ + interface DocumentHighlightsItem { + /** + * File containing highlight spans. + */ + file: string; + /** + * Spans to highlight in file. + */ + highlightSpans: HighlightSpan[]; + } + /** + * Response for a DocumentHighlightsRequest request. + */ + interface DocumentHighlightsResponse extends Response { + body?: DocumentHighlightsItem[]; + } + /** + * Find references request; value of command field is + * "references". Return response giving the file locations that + * reference the symbol found in file at location line, col. + */ + interface ReferencesRequest extends FileLocationRequest { + command: CommandTypes.References; + } + interface ReferencesResponseItem extends FileSpan { + /** Text of line containing the reference. Including this + * with the response avoids latency of editor loading files + * to show text of reference line (the server already has + * loaded the referencing files). + */ + lineText: string; + /** + * True if reference is a write location, false otherwise. + */ + isWriteAccess: boolean; + /** + * True if reference is a definition, false otherwise. + */ + isDefinition: boolean; + } + /** + * The body of a "references" response message. + */ + interface ReferencesResponseBody { + /** + * The file locations referencing the symbol. + */ + refs: ReferencesResponseItem[]; + /** + * The name of the symbol. + */ + symbolName: string; + /** + * The start character offset of the symbol (on the line provided by the references request). + */ + symbolStartOffset: number; + /** + * The full display name of the symbol. + */ + symbolDisplayString: string; + } + /** + * Response to "references" request. + */ + interface ReferencesResponse extends Response { + body?: ReferencesResponseBody; + } + /** + * Argument for RenameRequest request. + */ + interface RenameRequestArgs extends FileLocationRequestArgs { + /** + * Should text at specified location be found/changed in comments? + */ + findInComments?: boolean; + /** + * Should text at specified location be found/changed in strings? + */ + findInStrings?: boolean; + } + /** + * Rename request; value of command field is "rename". Return + * response giving the file locations that reference the symbol + * found in file at location line, col. Also return full display + * name of the symbol so that client can print it unambiguously. + */ + interface RenameRequest extends FileLocationRequest { + command: CommandTypes.Rename; + arguments: RenameRequestArgs; + } + /** + * Information about the item to be renamed. + */ + interface RenameInfo { + /** + * True if item can be renamed. + */ + canRename: boolean; + /** + * Error message if item can not be renamed. + */ + localizedErrorMessage?: string; + /** + * Display name of the item to be renamed. + */ + displayName: string; + /** + * Full display name of item to be renamed. + */ + fullDisplayName: string; + /** + * The items's kind (such as 'className' or 'parameterName' or plain 'text'). + */ + kind: ScriptElementKind; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers: string; + } + /** + * A group of text spans, all in 'file'. + */ + interface SpanGroup { + /** The file to which the spans apply */ + file: string; + /** The text spans in this group */ + locs: TextSpan[]; + } + interface RenameResponseBody { + /** + * Information about the item to be renamed. + */ + info: RenameInfo; + /** + * An array of span groups (one per file) that refer to the item to be renamed. + */ + locs: ReadonlyArray; + } + /** + * Rename response message. + */ + interface RenameResponse extends Response { + body?: RenameResponseBody; + } + /** + * Represents a file in external project. + * External project is project whose set of files, compilation options and open\close state + * is maintained by the client (i.e. if all this data come from .csproj file in Visual Studio). + * External project will exist even if all files in it are closed and should be closed explicitly. + * If external project includes one or more tsconfig.json/jsconfig.json files then tsserver will + * create configured project for every config file but will maintain a link that these projects were created + * as a result of opening external project so they should be removed once external project is closed. + */ + interface ExternalFile { + /** + * Name of file file + */ + fileName: string; + /** + * Script kind of the file + */ + scriptKind?: ScriptKindName | ts.ScriptKind; + /** + * Whether file has mixed content (i.e. .cshtml file that combines html markup with C#/JavaScript) + */ + hasMixedContent?: boolean; + /** + * Content of the file + */ + content?: string; + } + /** + * Represent an external project + */ + interface ExternalProject { + /** + * Project name + */ + projectFileName: string; + /** + * List of root files in project + */ + rootFiles: ExternalFile[]; + /** + * Compiler options for the project + */ + options: ExternalProjectCompilerOptions; + /** + * @deprecated typingOptions. Use typeAcquisition instead + */ + typingOptions?: TypeAcquisition; + /** + * Explicitly specified type acquisition for the project + */ + typeAcquisition?: TypeAcquisition; + } + interface CompileOnSaveMixin { + /** + * If compile on save is enabled for the project + */ + compileOnSave?: boolean; + } + /** + * For external projects, some of the project settings are sent together with + * compiler settings. + */ + type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin; + /** + * Represents a set of changes that happen in project + */ + interface ProjectChanges { + /** + * List of added files + */ + added: string[]; + /** + * List of removed files + */ + removed: string[]; + /** + * List of updated files + */ + updated: string[]; + } + /** + * Information found in a configure request. + */ + interface ConfigureRequestArguments { + /** + * Information about the host, for example 'Emacs 24.4' or + * 'Sublime Text version 3075' + */ + hostInfo?: string; + /** + * If present, tab settings apply only to this file. + */ + file?: string; + /** + * The format options to use during formatting and other code editing features. + */ + formatOptions?: FormatCodeSettings; + /** + * The host's additional supported .js file extensions + */ + extraFileExtensions?: JsFileExtensionInfo[]; + } + /** + * Configure request; value of command field is "configure". Specifies + * host information, such as host type, tab size, and indent size. + */ + interface ConfigureRequest extends Request { + command: CommandTypes.Configure; + arguments: ConfigureRequestArguments; + } + /** + * Response to "configure" request. This is just an acknowledgement, so + * no body field is required. + */ + interface ConfigureResponse extends Response { + } + /** + * Information found in an "open" request. + */ + interface OpenRequestArgs extends FileRequestArgs { + /** + * Used when a version of the file content is known to be more up to date than the one on disk. + * Then the known content will be used upon opening instead of the disk copy + */ + fileContent?: string; + /** + * Used to specify the script kind of the file explicitly. It could be one of the following: + * "TS", "JS", "TSX", "JSX" + */ + scriptKindName?: ScriptKindName; + /** + * Used to limit the searching for project config file. If given the searching will stop at this + * root path; otherwise it will go all the way up to the dist root path. + */ + projectRootPath?: string; + } + type ScriptKindName = "TS" | "JS" | "TSX" | "JSX"; + /** + * Open request; value of command field is "open". Notify the + * server that the client has file open. The server will not + * monitor the filesystem for changes in this file and will assume + * that the client is updating the server (using the change and/or + * reload messages) when the file changes. Server does not currently + * send a response to an open request. + */ + interface OpenRequest extends Request { + command: CommandTypes.Open; + arguments: OpenRequestArgs; + } + /** + * Request to open or update external project + */ + interface OpenExternalProjectRequest extends Request { + command: CommandTypes.OpenExternalProject; + arguments: OpenExternalProjectArgs; + } + /** + * Arguments to OpenExternalProjectRequest request + */ + type OpenExternalProjectArgs = ExternalProject; + /** + * Request to open multiple external projects + */ + interface OpenExternalProjectsRequest extends Request { + command: CommandTypes.OpenExternalProjects; + arguments: OpenExternalProjectsArgs; + } + /** + * Arguments to OpenExternalProjectsRequest + */ + interface OpenExternalProjectsArgs { + /** + * List of external projects to open or update + */ + projects: ExternalProject[]; + } + /** + * Response to OpenExternalProjectRequest request. This is just an acknowledgement, so + * no body field is required. + */ + interface OpenExternalProjectResponse extends Response { + } + /** + * Response to OpenExternalProjectsRequest request. This is just an acknowledgement, so + * no body field is required. + */ + interface OpenExternalProjectsResponse extends Response { + } + /** + * Request to close external project. + */ + interface CloseExternalProjectRequest extends Request { + command: CommandTypes.CloseExternalProject; + arguments: CloseExternalProjectRequestArgs; + } + /** + * Arguments to CloseExternalProjectRequest request + */ + interface CloseExternalProjectRequestArgs { + /** + * Name of the project to close + */ + projectFileName: string; + } + /** + * Response to CloseExternalProjectRequest request. This is just an acknowledgement, so + * no body field is required. + */ + interface CloseExternalProjectResponse extends Response { + } + /** + * Request to set compiler options for inferred projects. + * External projects are opened / closed explicitly. + * Configured projects are opened when user opens loose file that has 'tsconfig.json' or 'jsconfig.json' anywhere in one of containing folders. + * This configuration file will be used to obtain a list of files and configuration settings for the project. + * Inferred projects are created when user opens a loose file that is not the part of external project + * or configured project and will contain only open file and transitive closure of referenced files if 'useOneInferredProject' is false, + * or all open loose files and its transitive closure of referenced files if 'useOneInferredProject' is true. + */ + interface SetCompilerOptionsForInferredProjectsRequest extends Request { + command: CommandTypes.CompilerOptionsForInferredProjects; + arguments: SetCompilerOptionsForInferredProjectsArgs; + } + /** + * Argument for SetCompilerOptionsForInferredProjectsRequest request. + */ + interface SetCompilerOptionsForInferredProjectsArgs { + /** + * Compiler options to be used with inferred projects. + */ + options: ExternalProjectCompilerOptions; + /** + * Specifies the project root path used to scope compiler options. + * It is an error to provide this property if the server has not been started with + * `useInferredProjectPerProjectRoot` enabled. + */ + projectRootPath?: string; + } + /** + * Response to SetCompilerOptionsForInferredProjectsResponse request. This is just an acknowledgement, so + * no body field is required. + */ + interface SetCompilerOptionsForInferredProjectsResponse extends Response { + } + /** + * Exit request; value of command field is "exit". Ask the server process + * to exit. + */ + interface ExitRequest extends Request { + command: CommandTypes.Exit; + } + /** + * Close request; value of command field is "close". Notify the + * server that the client has closed a previously open file. If + * file is still referenced by open files, the server will resume + * monitoring the filesystem for changes to file. Server does not + * currently send a response to a close request. + */ + interface CloseRequest extends FileRequest { + command: CommandTypes.Close; + } + /** + * Request to obtain the list of files that should be regenerated if target file is recompiled. + * NOTE: this us query-only operation and does not generate any output on disk. + */ + interface CompileOnSaveAffectedFileListRequest extends FileRequest { + command: CommandTypes.CompileOnSaveAffectedFileList; + } + /** + * Contains a list of files that should be regenerated in a project + */ + interface CompileOnSaveAffectedFileListSingleProject { + /** + * Project name + */ + projectFileName: string; + /** + * List of files names that should be recompiled + */ + fileNames: string[]; + /** + * true if project uses outFile or out compiler option + */ + projectUsesOutFile: boolean; + } + /** + * Response for CompileOnSaveAffectedFileListRequest request; + */ + interface CompileOnSaveAffectedFileListResponse extends Response { + body: CompileOnSaveAffectedFileListSingleProject[]; + } + /** + * Request to recompile the file. All generated outputs (.js, .d.ts or .js.map files) is written on disk. + */ + interface CompileOnSaveEmitFileRequest extends FileRequest { + command: CommandTypes.CompileOnSaveEmitFile; + arguments: CompileOnSaveEmitFileRequestArgs; + } + /** + * Arguments for CompileOnSaveEmitFileRequest + */ + interface CompileOnSaveEmitFileRequestArgs extends FileRequestArgs { + /** + * if true - then file should be recompiled even if it does not have any changes. + */ + forced?: boolean; + } + /** + * Quickinfo request; value of command field is + * "quickinfo". Return response giving a quick type and + * documentation string for the symbol found in file at location + * line, col. + */ + interface QuickInfoRequest extends FileLocationRequest { + command: CommandTypes.Quickinfo; + } + /** + * Body of QuickInfoResponse. + */ + interface QuickInfoResponseBody { + /** + * The symbol's kind (such as 'className' or 'parameterName' or plain 'text'). + */ + kind: ScriptElementKind; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers: string; + /** + * Starting file location of symbol. + */ + start: Location; + /** + * One past last character of symbol. + */ + end: Location; + /** + * Type and kind of symbol. + */ + displayString: string; + /** + * Documentation associated with symbol. + */ + documentation: string; + /** + * JSDoc tags associated with symbol. + */ + tags: JSDocTagInfo[]; + } + /** + * Quickinfo response message. + */ + interface QuickInfoResponse extends Response { + body?: QuickInfoResponseBody; + } + /** + * Arguments for format messages. + */ + interface FormatRequestArgs extends FileLocationRequestArgs { + /** + * Last line of range for which to format text in file. + */ + endLine: number; + /** + * Character offset on last line of range for which to format text in file. + */ + endOffset: number; + /** + * Format options to be used. + */ + options?: FormatCodeSettings; + } + /** + * Format request; value of command field is "format". Return + * response giving zero or more edit instructions. The edit + * instructions will be sorted in file order. Applying the edit + * instructions in reverse to file will result in correctly + * reformatted text. + */ + interface FormatRequest extends FileLocationRequest { + command: CommandTypes.Format; + arguments: FormatRequestArgs; + } + /** + * Object found in response messages defining an editing + * instruction for a span of text in source code. The effect of + * this instruction is to replace the text starting at start and + * ending one character before end with newText. For an insertion, + * the text span is empty. For a deletion, newText is empty. + */ + interface CodeEdit { + /** + * First character of the text span to edit. + */ + start: Location; + /** + * One character past last character of the text span to edit. + */ + end: Location; + /** + * Replace the span defined above with this string (may be + * the empty string). + */ + newText: string; + } + interface FileCodeEdits { + fileName: string; + textChanges: CodeEdit[]; + } + interface CodeFixResponse extends Response { + /** The code actions that are available */ + body?: CodeAction[]; + } + interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileCodeEdits[]; + } + /** + * Format and format on key response message. + */ + interface FormatResponse extends Response { + body?: CodeEdit[]; + } + /** + * Arguments for format on key messages. + */ + interface FormatOnKeyRequestArgs extends FileLocationRequestArgs { + /** + * Key pressed (';', '\n', or '}'). + */ + key: string; + options?: FormatCodeSettings; + } + /** + * Format on key request; value of command field is + * "formatonkey". Given file location and key typed (as string), + * return response giving zero or more edit instructions. The + * edit instructions will be sorted in file order. Applying the + * edit instructions in reverse to file will result in correctly + * reformatted text. + */ + interface FormatOnKeyRequest extends FileLocationRequest { + command: CommandTypes.Formatonkey; + arguments: FormatOnKeyRequestArgs; + } + /** + * Arguments for completions messages. + */ + interface CompletionsRequestArgs extends FileLocationRequestArgs { + /** + * Optional prefix to apply to possible completions. + */ + prefix?: string; + } + /** + * Completions request; value of command field is "completions". + * Given a file location (file, line, col) and a prefix (which may + * be the empty string), return the possible completions that + * begin with prefix. + */ + interface CompletionsRequest extends FileLocationRequest { + command: CommandTypes.Completions; + arguments: CompletionsRequestArgs; + } + /** + * Arguments for completion details request. + */ + interface CompletionDetailsRequestArgs extends FileLocationRequestArgs { + /** + * Names of one or more entries for which to obtain details. + */ + entryNames: string[]; + } + /** + * Completion entry details request; value of command field is + * "completionEntryDetails". Given a file location (file, line, + * col) and an array of completion entry names return more + * detailed information for each completion entry. + */ + interface CompletionDetailsRequest extends FileLocationRequest { + command: CommandTypes.CompletionDetails; + arguments: CompletionDetailsRequestArgs; + } + /** + * Part of a symbol description. + */ + interface SymbolDisplayPart { + /** + * Text of an item describing the symbol. + */ + text: string; + /** + * The symbol's kind (such as 'className' or 'parameterName' or plain 'text'). + */ + kind: string; + } + /** + * An item found in a completion response. + */ + interface CompletionEntry { + /** + * The symbol's name. + */ + name: string; + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ + kind: ScriptElementKind; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers: string; + /** + * A string that is used for comparing completion items so that they can be ordered. This + * is often the same as the name but may be different in certain circumstances. + */ + sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. If present, + * this span should be used instead of the default one. + */ + replacementSpan?: TextSpan; + } + /** + * Additional completion entry details, available on demand + */ + interface CompletionEntryDetails { + /** + * The symbol's name. + */ + name: string; + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ + kind: ScriptElementKind; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers: string; + /** + * Display parts of the symbol (similar to quick info). + */ + displayParts: SymbolDisplayPart[]; + /** + * Documentation strings for the symbol. + */ + documentation: SymbolDisplayPart[]; + /** + * JSDoc tags for the symbol. + */ + tags: JSDocTagInfo[]; + } + interface CompletionsResponse extends Response { + body?: CompletionEntry[]; + } + interface CompletionDetailsResponse extends Response { + body?: CompletionEntryDetails[]; + } + /** + * Signature help information for a single parameter + */ + interface SignatureHelpParameter { + /** + * The parameter's name + */ + name: string; + /** + * Documentation of the parameter. + */ + documentation: SymbolDisplayPart[]; + /** + * Display parts of the parameter. + */ + displayParts: SymbolDisplayPart[]; + /** + * Whether the parameter is optional or not. + */ + isOptional: boolean; + } + /** + * Represents a single signature to show in signature help. + */ + interface SignatureHelpItem { + /** + * Whether the signature accepts a variable number of arguments. + */ + isVariadic: boolean; + /** + * The prefix display parts. + */ + prefixDisplayParts: SymbolDisplayPart[]; + /** + * The suffix display parts. + */ + suffixDisplayParts: SymbolDisplayPart[]; + /** + * The separator display parts. + */ + separatorDisplayParts: SymbolDisplayPart[]; + /** + * The signature helps items for the parameters. + */ + parameters: SignatureHelpParameter[]; + /** + * The signature's documentation + */ + documentation: SymbolDisplayPart[]; + /** + * The signature's JSDoc tags + */ + tags: JSDocTagInfo[]; + } + /** + * Signature help items found in the response of a signature help request. + */ + interface SignatureHelpItems { + /** + * The signature help items. + */ + items: SignatureHelpItem[]; + /** + * The span for which signature help should appear on a signature + */ + applicableSpan: TextSpan; + /** + * The item selected in the set of available help items. + */ + selectedItemIndex: number; + /** + * The argument selected in the set of parameters. + */ + argumentIndex: number; + /** + * The argument count + */ + argumentCount: number; + } + /** + * Arguments of a signature help request. + */ + interface SignatureHelpRequestArgs extends FileLocationRequestArgs { + } + /** + * Signature help request; value of command field is "signatureHelp". + * Given a file location (file, line, col), return the signature + * help. + */ + interface SignatureHelpRequest extends FileLocationRequest { + command: CommandTypes.SignatureHelp; + arguments: SignatureHelpRequestArgs; + } + /** + * Response object for a SignatureHelpRequest. + */ + interface SignatureHelpResponse extends Response { + body?: SignatureHelpItems; + } + /** + * Synchronous request for semantic diagnostics of one file. + */ + interface SemanticDiagnosticsSyncRequest extends FileRequest { + command: CommandTypes.SemanticDiagnosticsSync; + arguments: SemanticDiagnosticsSyncRequestArgs; + } + interface SemanticDiagnosticsSyncRequestArgs extends FileRequestArgs { + includeLinePosition?: boolean; + } + /** + * Response object for synchronous sematic diagnostics request. + */ + interface SemanticDiagnosticsSyncResponse extends Response { + body?: Diagnostic[] | DiagnosticWithLinePosition[]; + } + /** + * Synchronous request for syntactic diagnostics of one file. + */ + interface SyntacticDiagnosticsSyncRequest extends FileRequest { + command: CommandTypes.SyntacticDiagnosticsSync; + arguments: SyntacticDiagnosticsSyncRequestArgs; + } + interface SyntacticDiagnosticsSyncRequestArgs extends FileRequestArgs { + includeLinePosition?: boolean; + } + /** + * Response object for synchronous syntactic diagnostics request. + */ + interface SyntacticDiagnosticsSyncResponse extends Response { + body?: Diagnostic[] | DiagnosticWithLinePosition[]; + } + /** + * Arguments for GeterrForProject request. + */ + interface GeterrForProjectRequestArgs { + /** + * the file requesting project error list + */ + file: string; + /** + * Delay in milliseconds to wait before starting to compute + * errors for the files in the file list + */ + delay: number; + } + /** + * GeterrForProjectRequest request; value of command field is + * "geterrForProject". It works similarly with 'Geterr', only + * it request for every file in this project. + */ + interface GeterrForProjectRequest extends Request { + command: CommandTypes.GeterrForProject; + arguments: GeterrForProjectRequestArgs; + } + /** + * Arguments for geterr messages. + */ + interface GeterrRequestArgs { + /** + * List of file names for which to compute compiler errors. + * The files will be checked in list order. + */ + files: string[]; + /** + * Delay in milliseconds to wait before starting to compute + * errors for the files in the file list + */ + delay: number; + } + /** + * Geterr request; value of command field is "geterr". Wait for + * delay milliseconds and then, if during the wait no change or + * reload messages have arrived for the first file in the files + * list, get the syntactic errors for the file, field requests, + * and then get the semantic errors for the file. Repeat with a + * smaller delay for each subsequent file on the files list. Best + * practice for an editor is to send a file list containing each + * file that is currently visible, in most-recently-used order. + */ + interface GeterrRequest extends Request { + command: CommandTypes.Geterr; + arguments: GeterrRequestArgs; + } + type RequestCompletedEventName = "requestCompleted"; + /** + * Event that is sent when server have finished processing request with specified id. + */ + interface RequestCompletedEvent extends Event { + event: RequestCompletedEventName; + body: RequestCompletedEventBody; + } + interface RequestCompletedEventBody { + request_seq: number; + } + /** + * Item of diagnostic information found in a DiagnosticEvent message. + */ + interface Diagnostic { + /** + * Starting file location at which text applies. + */ + start: Location; + /** + * The last file location at which the text applies. + */ + end: Location; + /** + * Text of diagnostic message. + */ + text: string; + /** + * The category of the diagnostic message, e.g. "error" vs. "warning" + */ + category: string; + /** + * The error code of the diagnostic message. + */ + code?: number; + /** + * The name of the plugin reporting the message. + */ + source?: string; + } + interface DiagnosticWithFileName extends Diagnostic { + /** + * Name of the file the diagnostic is in + */ + fileName: string; + } + interface DiagnosticEventBody { + /** + * The file for which diagnostic information is reported. + */ + file: string; + /** + * An array of diagnostic information items. + */ + diagnostics: Diagnostic[]; + } + /** + * Event message for "syntaxDiag" and "semanticDiag" event types. + * These events provide syntactic and semantic errors for a file. + */ + interface DiagnosticEvent extends Event { + body?: DiagnosticEventBody; + } + interface ConfigFileDiagnosticEventBody { + /** + * The file which trigged the searching and error-checking of the config file + */ + triggerFile: string; + /** + * The name of the found config file. + */ + configFile: string; + /** + * An arry of diagnostic information items for the found config file. + */ + diagnostics: DiagnosticWithFileName[]; + } + /** + * Event message for "configFileDiag" event type. + * This event provides errors for a found config file. + */ + interface ConfigFileDiagnosticEvent extends Event { + body?: ConfigFileDiagnosticEventBody; + event: "configFileDiag"; + } + type ProjectLanguageServiceStateEventName = "projectLanguageServiceState"; + interface ProjectLanguageServiceStateEvent extends Event { + event: ProjectLanguageServiceStateEventName; + body?: ProjectLanguageServiceStateEventBody; + } + interface ProjectLanguageServiceStateEventBody { + /** + * Project name that has changes in the state of language service. + * For configured projects this will be the config file path. + * For external projects this will be the name of the projects specified when project was open. + * For inferred projects this event is not raised. + */ + projectName: string; + /** + * True if language service state switched from disabled to enabled + * and false otherwise. + */ + languageServiceEnabled: boolean; + } + /** + * Arguments for reload request. + */ + interface ReloadRequestArgs extends FileRequestArgs { + /** + * Name of temporary file from which to reload file + * contents. May be same as file. + */ + tmpfile: string; + } + /** + * Reload request message; value of command field is "reload". + * Reload contents of file with name given by the 'file' argument + * from temporary file with name given by the 'tmpfile' argument. + * The two names can be identical. + */ + interface ReloadRequest extends FileRequest { + command: CommandTypes.Reload; + arguments: ReloadRequestArgs; + } + /** + * Response to "reload" request. This is just an acknowledgement, so + * no body field is required. + */ + interface ReloadResponse extends Response { + } + /** + * Arguments for saveto request. + */ + interface SavetoRequestArgs extends FileRequestArgs { + /** + * Name of temporary file into which to save server's view of + * file contents. + */ + tmpfile: string; + } + /** + * Saveto request message; value of command field is "saveto". + * For debugging purposes, save to a temporaryfile (named by + * argument 'tmpfile') the contents of file named by argument + * 'file'. The server does not currently send a response to a + * "saveto" request. + */ + interface SavetoRequest extends FileRequest { + command: CommandTypes.Saveto; + arguments: SavetoRequestArgs; + } + /** + * Arguments for navto request message. + */ + interface NavtoRequestArgs extends FileRequestArgs { + /** + * Search term to navigate to from current location; term can + * be '.*' or an identifier prefix. + */ + searchValue: string; + /** + * Optional limit on the number of items to return. + */ + maxResultCount?: number; + /** + * Optional flag to indicate we want results for just the current file + * or the entire project. + */ + currentFileOnly?: boolean; + projectFileName?: string; + } + /** + * Navto request message; value of command field is "navto". + * Return list of objects giving file locations and symbols that + * match the search term given in argument 'searchTerm'. The + * context for the search is given by the named file. + */ + interface NavtoRequest extends FileRequest { + command: CommandTypes.Navto; + arguments: NavtoRequestArgs; + } + /** + * An item found in a navto response. + */ + interface NavtoItem { + /** + * The symbol's name. + */ + name: string; + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ + kind: ScriptElementKind; + /** + * exact, substring, or prefix. + */ + matchKind?: string; + /** + * If this was a case sensitive or insensitive match. + */ + isCaseSensitive?: boolean; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers?: string; + /** + * The file in which the symbol is found. + */ + file: string; + /** + * The location within file at which the symbol is found. + */ + start: Location; + /** + * One past the last character of the symbol. + */ + end: Location; + /** + * Name of symbol's container symbol (if any); for example, + * the class name if symbol is a class member. + */ + containerName?: string; + /** + * Kind of symbol's container symbol (if any). + */ + containerKind?: ScriptElementKind; + } + /** + * Navto response message. Body is an array of navto items. Each + * item gives a symbol that matched the search term. + */ + interface NavtoResponse extends Response { + body?: NavtoItem[]; + } + /** + * Arguments for change request message. + */ + interface ChangeRequestArgs extends FormatRequestArgs { + /** + * Optional string to insert at location (file, line, offset). + */ + insertString?: string; + } + /** + * Change request message; value of command field is "change". + * Update the server's view of the file named by argument 'file'. + * Server does not currently send a response to a change request. + */ + interface ChangeRequest extends FileLocationRequest { + command: CommandTypes.Change; + arguments: ChangeRequestArgs; + } + /** + * Response to "brace" request. + */ + interface BraceResponse extends Response { + body?: TextSpan[]; + } + /** + * Brace matching request; value of command field is "brace". + * Return response giving the file locations of matching braces + * found in file at location line, offset. + */ + interface BraceRequest extends FileLocationRequest { + command: CommandTypes.Brace; + } + /** + * NavBar items request; value of command field is "navbar". + * Return response giving the list of navigation bar entries + * extracted from the requested file. + */ + interface NavBarRequest extends FileRequest { + command: CommandTypes.NavBar; + } + /** + * NavTree request; value of command field is "navtree". + * Return response giving the navigation tree of the requested file. + */ + interface NavTreeRequest extends FileRequest { + command: CommandTypes.NavTree; + } + interface NavigationBarItem { + /** + * The item's display text. + */ + text: string; + /** + * The symbol's kind (such as 'className' or 'parameterName'). + */ + kind: ScriptElementKind; + /** + * Optional modifiers for the kind (such as 'public'). + */ + kindModifiers?: string; + /** + * The definition locations of the item. + */ + spans: TextSpan[]; + /** + * Optional children. + */ + childItems?: NavigationBarItem[]; + /** + * Number of levels deep this item should appear. + */ + indent: number; + } + /** protocol.NavigationTree is identical to ts.NavigationTree, except using protocol.TextSpan instead of ts.TextSpan */ + interface NavigationTree { + text: string; + kind: ScriptElementKind; + kindModifiers: string; + spans: TextSpan[]; + childItems?: NavigationTree[]; + } + type TelemetryEventName = "telemetry"; + interface TelemetryEvent extends Event { + event: TelemetryEventName; + body: TelemetryEventBody; + } + interface TelemetryEventBody { + telemetryEventName: string; + payload: any; + } + type TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed"; + interface TypesInstallerInitializationFailedEvent extends Event { + event: TypesInstallerInitializationFailedEventName; + body: TypesInstallerInitializationFailedEventBody; + } + interface TypesInstallerInitializationFailedEventBody { + message: string; + } + type TypingsInstalledTelemetryEventName = "typingsInstalled"; + interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody { + telemetryEventName: TypingsInstalledTelemetryEventName; + payload: TypingsInstalledTelemetryEventPayload; + } + interface TypingsInstalledTelemetryEventPayload { + /** + * Comma separated list of installed typing packages + */ + installedPackages: string; + /** + * true if install request succeeded, otherwise - false + */ + installSuccess: boolean; + /** + * version of typings installer + */ + typingsInstallerVersion: string; + } + type BeginInstallTypesEventName = "beginInstallTypes"; + type EndInstallTypesEventName = "endInstallTypes"; + interface BeginInstallTypesEvent extends Event { + event: BeginInstallTypesEventName; + body: BeginInstallTypesEventBody; + } + interface EndInstallTypesEvent extends Event { + event: EndInstallTypesEventName; + body: EndInstallTypesEventBody; + } + interface InstallTypesEventBody { + /** + * correlation id to match begin and end events + */ + eventId: number; + /** + * list of packages to install + */ + packages: ReadonlyArray; + } + interface BeginInstallTypesEventBody extends InstallTypesEventBody { + } + interface EndInstallTypesEventBody extends InstallTypesEventBody { + /** + * true if installation succeeded, otherwise false + */ + success: boolean; + } + interface NavBarResponse extends Response { + body?: NavigationBarItem[]; + } + interface NavTreeResponse extends Response { + body?: NavigationTree; + } + enum IndentStyle { + None = "None", + Block = "Block", + Smart = "Smart", + } + interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle | ts.IndentStyle; + } + interface FormatCodeSettings extends EditorSettings { + insertSpaceAfterCommaDelimiter?: boolean; + insertSpaceAfterSemicolonInForStatements?: boolean; + insertSpaceBeforeAndAfterBinaryOperators?: boolean; + insertSpaceAfterConstructor?: boolean; + insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + insertSpaceAfterTypeAssertion?: boolean; + insertSpaceBeforeFunctionParenthesis?: boolean; + placeOpenBraceOnNewLineForFunctions?: boolean; + placeOpenBraceOnNewLineForControlBlocks?: boolean; + } + interface CompilerOptions { + allowJs?: boolean; + allowSyntheticDefaultImports?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + alwaysStrict?: boolean; + baseUrl?: string; + charset?: string; + checkJs?: boolean; + declaration?: boolean; + declarationDir?: string; + disableSizeLimit?: boolean; + downlevelIteration?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + importHelpers?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + isolatedModules?: boolean; + jsx?: JsxEmit | ts.JsxEmit; + lib?: string[]; + locale?: string; + mapRoot?: string; + maxNodeModuleJsDepth?: number; + module?: ModuleKind | ts.ModuleKind; + moduleResolution?: ModuleResolutionKind | ts.ModuleResolutionKind; + newLine?: NewLineKind | ts.NewLineKind; + noEmit?: boolean; + noEmitHelpers?: boolean; + noEmitOnError?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitReturns?: boolean; + noImplicitThis?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noImplicitUseStrict?: boolean; + noLib?: boolean; + noResolve?: boolean; + out?: string; + outDir?: string; + outFile?: string; + paths?: MapLike; + plugins?: PluginImport[]; + preserveConstEnums?: boolean; + preserveSymlinks?: boolean; + project?: string; + reactNamespace?: string; + removeComments?: boolean; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + strict?: boolean; + strictNullChecks?: boolean; + suppressExcessPropertyErrors?: boolean; + suppressImplicitAnyIndexErrors?: boolean; + target?: ScriptTarget | ts.ScriptTarget; + traceResolution?: boolean; + types?: string[]; + /** Paths used to used to compute primary types search locations */ + typeRoots?: string[]; + [option: string]: CompilerOptionsValue | undefined; + } + enum JsxEmit { + None = "None", + Preserve = "Preserve", + ReactNative = "ReactNative", + React = "React", + } + enum ModuleKind { + None = "None", + CommonJS = "CommonJS", + AMD = "AMD", + UMD = "UMD", + System = "System", + ES6 = "ES6", + ES2015 = "ES2015", + ESNext = "ESNext", + } + enum ModuleResolutionKind { + Classic = "Classic", + Node = "Node", + } + enum NewLineKind { + Crlf = "Crlf", + Lf = "Lf", + } + enum ScriptTarget { + ES3 = "ES3", + ES5 = "ES5", + ES6 = "ES6", + ES2015 = "ES2015", + ES2016 = "ES2016", + ES2017 = "ES2017", + ESNext = "ESNext", + } +} +declare namespace ts.server { + interface ServerCancellationToken extends HostCancellationToken { + setRequest(requestId: number): void; + resetRequest(requestId: number): void; + } + const nullCancellationToken: ServerCancellationToken; + interface PendingErrorCheck { + fileName: NormalizedPath; + project: Project; + } + interface EventSender { + event(payload: T, eventName: string): void; + } + type CommandNames = protocol.CommandTypes; + const CommandNames: any; + function formatMessage(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string; + interface SessionOptions { + host: ServerHost; + cancellationToken: ServerCancellationToken; + useSingleInferredProject: boolean; + useInferredProjectPerProjectRoot: boolean; + typingsInstaller: ITypingsInstaller; + byteLength: (buf: string, encoding?: string) => number; + hrtime: (start?: number[]) => number[]; + logger: Logger; + canUseEvents: boolean; + eventHandler?: ProjectServiceEventHandler; + throttleWaitMilliseconds?: number; + globalPlugins?: ReadonlyArray; + pluginProbeLocations?: ReadonlyArray; + allowLocalPluginLoads?: boolean; + } + class Session implements EventSender { + private readonly gcTimer; + protected projectService: ProjectService; + private changeSeq; + private currentRequestId; + private errorCheck; + private eventHandler; + private host; + private readonly cancellationToken; + protected readonly typingsInstaller: ITypingsInstaller; + private byteLength; + private hrtime; + protected logger: Logger; + private canUseEvents; + constructor(opts: SessionOptions); + private sendRequestCompletedEvent(requestId); + private defaultEventHandler(event); + logError(err: Error, cmd: string): void; + send(msg: protocol.Message): void; + configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ReadonlyArray): void; + event(info: T, eventName: string): void; + output(info: any, cmdName: string, reqSeq?: number, errorMsg?: string): void; + private semanticCheck(file, project); + private syntacticCheck(file, project); + private updateProjectStructure(); + private updateErrorCheck(next, checkList, ms, requireOpen?); + private cleanProjects(caption, projects); + private cleanup(); + private getEncodedSemanticClassifications(args); + private getProject(projectFileName); + private getConfigFileAndProject(args); + private getConfigFileDiagnostics(configFile, project, includeLinePosition); + private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics); + private getCompilerOptionsDiagnostics(args); + private convertToDiagnosticsWithLinePosition(diagnostics, scriptInfo); + private getDiagnosticsWorker(args, isSemantic, selector, includeLinePosition); + private getDefinition(args, simplifiedResult); + private getTypeDefinition(args); + private getImplementation(args, simplifiedResult); + private getOccurrences(args); + private getSyntacticDiagnosticsSync(args); + private getSemanticDiagnosticsSync(args); + private getDocumentHighlights(args, simplifiedResult); + private setCompilerOptionsForInferredProjects(args); + private getProjectInfo(args); + private getProjectInfoWorker(uncheckedFileName, projectFileName, needFileNameList, excludeConfigFiles); + private getRenameInfo(args); + private getProjects(args); + private getDefaultProject(args); + private getRenameLocations(args, simplifiedResult); + private getReferences(args, simplifiedResult); + /** + * @param fileName is the name of the file to be opened + * @param fileContent is a version of the file content that is known to be more up to date than the one on disk + */ + private openClientFile(fileName, fileContent?, scriptKind?, projectRootPath?); + private getPosition(args, scriptInfo); + private getFileAndProject(args, errorOnMissingProject?); + private getFileAndProjectWithoutRefreshingInferredProjects(args, errorOnMissingProject?); + private getFileAndProjectWorker(uncheckedFileName, projectFileName, refreshInferredProjects, errorOnMissingProject); + private getOutliningSpans(args); + private getTodoComments(args); + private getDocCommentTemplate(args); + private getSpanOfEnclosingComment(args); + private getIndentation(args); + private getBreakpointStatement(args); + private getNameOrDottedNameSpan(args); + private isValidBraceCompletion(args); + private getQuickInfoWorker(args, simplifiedResult); + private getFormattingEditsForRange(args); + private getFormattingEditsForRangeFull(args); + private getFormattingEditsForDocumentFull(args); + private getFormattingEditsAfterKeystrokeFull(args); + private getFormattingEditsAfterKeystroke(args); + private getCompletions(args, simplifiedResult); + private getCompletionEntryDetails(args); + private getCompileOnSaveAffectedFileList(args); + private emitFile(args); + private getSignatureHelpItems(args, simplifiedResult); + private getDiagnostics(next, delay, fileNames); + private change(args); + private reload(args, reqSeq); + private saveToTmp(fileName, tempFileName); + private closeClientFile(fileName); + private decorateNavigationBarItems(items, scriptInfo); + private getNavigationBarItems(args, simplifiedResult); + private decorateNavigationTree(tree, scriptInfo); + private decorateSpan(span, scriptInfo); + private getNavigationTree(args, simplifiedResult); + private getNavigateToItems(args, simplifiedResult); + private getSupportedCodeFixes(); + private isLocation(locationOrSpan); + private extractPositionAndRange(args, scriptInfo); + private getApplicableRefactors(args); + private getEditsForRefactor(args, simplifiedResult); + private getCodeFixes(args, simplifiedResult); + private getStartAndEndPosition(args, scriptInfo); + private mapCodeAction(codeAction, scriptInfo); + private mapTextChangesToCodeEdits(project, textChanges); + private convertTextChangeToCodeEdit(change, scriptInfo); + private getBraceMatching(args, simplifiedResult); + private getDiagnosticsForProject(next, delay, fileName); + getCanonicalFileName(fileName: string): string; + exit(): void; + private notRequired(); + private requiredResponse(response); + private handlers; + addProtocolHandler(command: string, handler: (request: protocol.Request) => { + response?: any; + responseRequired: boolean; + }): void; + private setCurrentRequest(requestId); + private resetCurrentRequest(requestId); + executeWithRequestId(requestId: number, f: () => T): T; + executeCommand(request: protocol.Request): { + response?: any; + responseRequired?: boolean; + }; + onMessage(message: string): void; + } +} +declare namespace ts.server { + interface AbsolutePositionAndLineText { + absolutePosition: number; + lineText: string | undefined; + } + class ScriptVersionCache { + private changes; + private readonly versions; + private minVersion; + private currentVersion; + private static readonly changeNumberThreshold; + private static readonly changeLengthThreshold; + private static readonly maxVersions; + private versionToIndex(version); + private currentVersionToIndex(); + edit(pos: number, deleteLen: number, insertedText?: string): void; + reload(script: string): void; + getSnapshot(): IScriptSnapshot; + private _getSnapshot(); + getSnapshotVersion(): number; + getLineInfo(line: number): AbsolutePositionAndLineText; + lineOffsetToPosition(line: number, column: number): number; + positionToLineOffset(position: number): protocol.Location; + lineToTextSpan(line: number): TextSpan; + getTextChangesBetweenVersions(oldVersion: number, newVersion: number): TextChangeRange; + static fromString(script: string): ScriptVersionCache; + } +} +declare namespace ts.server { + class ScriptInfo { + private readonly host; + readonly fileName: NormalizedPath; + readonly scriptKind: ScriptKind; + hasMixedContent: boolean; + isDynamic: boolean; + /** + * All projects that include this file + */ + readonly containingProjects: Project[]; + private formatCodeSettings; + readonly path: Path; + private fileWatcher; + private textStorage; + private isOpen; + constructor(host: ServerHost, fileName: NormalizedPath, scriptKind: ScriptKind, hasMixedContent?: boolean, isDynamic?: boolean); + isScriptOpen(): boolean; + open(newText: string): void; + close(): void; + getSnapshot(): IScriptSnapshot; + getFormatCodeSettings(): FormatCodeSettings; + attachToProject(project: Project): boolean; + isAttached(project: Project): boolean; + detachFromProject(project: Project): void; + detachAllProjects(): void; + getDefaultProject(): Project; + registerFileUpdate(): void; + setFormatOptions(formatSettings: FormatCodeSettings): void; + setWatcher(watcher: FileWatcher): void; + stopWatcher(): void; + getLatestVersion(): string; + reload(script: string): void; + saveTo(fileName: string): void; + reloadFromFile(tempFileName?: NormalizedPath): void; + getLineInfo(line: number): AbsolutePositionAndLineText; + editContent(start: number, end: number, newText: string): void; + markContainingProjectsAsDirty(): void; + /** + * @param line 1 based index + */ + lineToTextSpan(line: number): TextSpan; + /** + * @param line 1 based index + * @param offset 1 based index + */ + lineOffsetToPosition(line: number, offset: number): number; + positionToLineOffset(position: number): protocol.Location; + isJavaScript(): boolean; + } +} +declare namespace ts.server { + class LSHost implements LanguageServiceHost, ModuleResolutionHost { + private readonly host; + private project; + private readonly cancellationToken; + private compilationSettings; + private readonly resolvedModuleNames; + private readonly resolvedTypeReferenceDirectives; + private readonly getCanonicalFileName; + private filesWithChangedSetOfUnresolvedImports; + private resolveModuleName; + readonly trace: (s: string) => void; + readonly realpath?: (path: string) => string; + constructor(host: ServerHost, project: Project, cancellationToken: HostCancellationToken); + dispose(): void; + startRecordingFilesWithChangedResolutions(): void; + finishRecordingFilesWithChangedResolutions(): Path[]; + private resolveNamesWithLocalCache(names, containingFile, cache, loader, getResult, getResultFileName, logChanges); + getNewLine(): string; + getProjectVersion(): string; + getCompilationSettings(): CompilerOptions; + useCaseSensitiveFileNames(): boolean; + getCancellationToken(): HostCancellationToken; + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[]; + getDefaultLibFileName(): string; + getScriptSnapshot(filename: string): IScriptSnapshot; + getScriptFileNames(): string[]; + getTypeRootsVersion(): number; + getScriptKind(fileName: string): ScriptKind; + getScriptVersion(filename: string): string; + getCurrentDirectory(): string; + resolvePath(path: string): string; + fileExists(file: string): boolean; + readFile(fileName: string): string | undefined; + directoryExists(path: string): boolean; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + getDirectories(path: string): string[]; + notifyFileRemoved(info: ScriptInfo): void; + setCompilationSettings(opt: CompilerOptions): void; + } +} +declare namespace ts.server { + interface ITypingsInstaller { + enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray): void; + attach(projectService: ProjectService): void; + onProjectClosed(p: Project): void; + readonly globalTypingsCacheLocation: string; + } + const nullTypingsInstaller: ITypingsInstaller; + class TypingsCache { + private readonly installer; + private readonly perProjectCache; + constructor(installer: ITypingsInstaller); + getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean): SortedReadonlyArray; + updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, newTypings: string[]): void; + deleteTypingsForProject(projectName: string): void; + onProjectClosed(project: Project): void; + } +} +declare namespace ts.server { + function shouldEmitFile(scriptInfo: ScriptInfo): boolean; + /** + * An abstract file info that maintains a shape signature. + */ + class BuilderFileInfo { + readonly scriptInfo: ScriptInfo; + readonly project: Project; + private lastCheckedShapeSignature; + constructor(scriptInfo: ScriptInfo, project: Project); + isExternalModuleOrHasOnlyAmbientExternalModules(): boolean; + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + private containsOnlyAmbientModules(sourceFile); + private computeHash(text); + private getSourceFile(); + /** + * @return {boolean} indicates if the shape signature has changed since last update. + */ + updateShapeSignature(): boolean; + } + interface Builder { + readonly project: Project; + getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; + onProjectUpdateGraph(): void; + emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean; + clear(): void; + } + function createBuilder(project: Project): Builder; +} +declare namespace ts.server { + enum ProjectKind { + Inferred = 0, + Configured = 1, + External = 2, + } + function allRootFilesAreJsOrDts(project: Project): boolean; + function allFilesAreJsOrDts(project: Project): boolean; + class UnresolvedImportsMap { + readonly perFileMap: Map>; + private version; + clear(): void; + getVersion(): number; + remove(path: Path): void; + get(path: Path): ReadonlyArray; + set(path: Path, value: ReadonlyArray): void; + } + interface PluginCreateInfo { + project: Project; + languageService: LanguageService; + languageServiceHost: LanguageServiceHost; + serverHost: ServerHost; + config: any; + } + interface PluginModule { + create(createInfo: PluginCreateInfo): LanguageService; + getExternalFiles?(proj: Project): string[]; + } + interface PluginModuleFactory { + (mod: { + typescript: typeof ts; + }): PluginModule; + } + abstract class Project { + private readonly projectName; + readonly projectKind: ProjectKind; + readonly projectService: ProjectService; + private documentRegistry; + private compilerOptions; + compileOnSaveEnabled: boolean; + private rootFiles; + private rootFilesMap; + private program; + private externalFiles; + private missingFilesMap; + private cachedUnresolvedImportsPerFile; + private lastCachedUnresolvedImportsList; + protected languageService: LanguageService; + languageServiceEnabled: boolean; + protected lsHost: LSHost; + builder: Builder; + /** + * Set of files names that were updated since the last call to getChangesSinceVersion. + */ + private updatedFileNames; + /** + * Set of files that was returned from the last call to getChangesSinceVersion. + */ + private lastReportedFileNames; + /** + * Last version that was reported. + */ + private lastReportedVersion; + /** + * Current project structure version. + * This property is changed in 'updateGraph' based on the set of files in program + */ + private projectStructureVersion; + /** + * Current version of the project state. It is changed when: + * - new root file was added/removed + * - edit happen in some file that is currently included in the project. + * This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project + */ + private projectStateVersion; + private typingFiles; + protected projectErrors: ReadonlyArray; + typesVersion: number; + isNonTsProject(): boolean; + isJsOnlyProject(): boolean; + getCachedUnresolvedImportsPerFile_TestOnly(): UnresolvedImportsMap; + static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {}; + constructor(projectName: string, projectKind: ProjectKind, projectService: ProjectService, documentRegistry: DocumentRegistry, hasExplicitListOfFiles: boolean, languageServiceEnabled: boolean, compilerOptions: CompilerOptions, compileOnSaveEnabled: boolean); + private setInternalCompilerOptionsForEmittingJsFiles(); + /** + * Get the errors that dont have any file name associated + */ + getGlobalProjectErrors(): ReadonlyArray; + getAllProjectErrors(): ReadonlyArray; + getLanguageService(ensureSynchronized?: boolean): LanguageService; + getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; + getProjectVersion(): string; + enableLanguageService(): void; + disableLanguageService(): void; + getProjectName(): string; + abstract getProjectRootPath(): string | undefined; + abstract getTypeAcquisition(): TypeAcquisition; + getExternalFiles(): SortedReadonlyArray; + getSourceFile(path: Path): SourceFile; + updateTypes(): void; + close(): void; + getCompilerOptions(): CompilerOptions; + hasRoots(): boolean; + getRootFiles(): NormalizedPath[]; + getRootFilesLSHost(): string[]; + getRootScriptInfos(): ScriptInfo[]; + getScriptInfos(): ScriptInfo[]; + getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean): EmitOutput; + getExcludedFiles(): ReadonlyArray; + getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean): NormalizedPath[]; + hasConfigFile(configFilePath: NormalizedPath): boolean; + getAllEmittableFiles(): string[]; + containsScriptInfo(info: ScriptInfo): boolean; + containsFile(filename: NormalizedPath, requireOpen?: boolean): boolean; + isRoot(info: ScriptInfo): boolean; + addRoot(info: ScriptInfo): void; + removeFile(info: ScriptInfo, detachFromProject?: boolean): void; + registerFileUpdate(fileName: string): void; + markAsDirty(): void; + private extractUnresolvedImportsFromSourceFile(file, result); + /** + * Updates set of files that contribute to this project + * @returns: true if set of files in the project stays the same and false - otherwise. + */ + updateGraph(): boolean; + private setTypings(typings); + private updateGraphWorker(); + isWatchedMissingFile(path: Path): boolean; + getScriptInfoLSHost(fileName: string): ScriptInfo; + getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo; + getScriptInfo(uncheckedFileName: string): ScriptInfo; + filesToString(): string; + setCompilerOptions(compilerOptions: CompilerOptions): void; + reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean; + getReferencedFiles(path: Path): Path[]; + protected removeRoot(info: ScriptInfo): void; + } + /** + * If a file is opened and no tsconfig (or jsconfig) is found, + * the file and its imports/references are put into an InferredProject. + */ + class InferredProject extends Project { + readonly projectRootPath: string | undefined; + private static readonly newName; + private _isJsInferredProject; + toggleJsInferredProject(isJsInferredProject: boolean): void; + setCompilerOptions(options?: CompilerOptions): void; + directoriesWatchedForTsconfig: string[]; + constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, projectRootPath?: string); + addRoot(info: ScriptInfo): void; + removeRoot(info: ScriptInfo): void; + getProjectRootPath(): string; + close(): void; + getTypeAcquisition(): TypeAcquisition; + } + /** + * If a file is opened, the server will look for a tsconfig (or jsconfig) + * and if successfull create a ConfiguredProject for it. + * Otherwise it will create an InferredProject. + */ + class ConfiguredProject extends Project { + private wildcardDirectories; + compileOnSaveEnabled: boolean; + private typeAcquisition; + private projectFileWatcher; + private directoryWatcher; + private directoriesWatchedForWildcards; + private typeRootsWatchers; + readonly canonicalConfigFilePath: NormalizedPath; + private plugins; + /** Used for configured projects which may have multiple open roots */ + openRefCount: number; + constructor(configFileName: NormalizedPath, projectService: ProjectService, documentRegistry: DocumentRegistry, hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions, wildcardDirectories: Map, languageServiceEnabled: boolean, compileOnSaveEnabled: boolean); + getConfigFilePath(): string; + enablePlugins(): void; + private enablePlugin(pluginConfigEntry, searchPaths); + private enableProxy(pluginModuleFactory, configEntry); + getProjectRootPath(): string; + setProjectErrors(projectErrors: ReadonlyArray): void; + setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; + getTypeAcquisition(): TypeAcquisition; + getExternalFiles(): SortedReadonlyArray; + watchConfigFile(callback: (project: ConfiguredProject) => void): void; + watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void): void; + watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void): void; + watchWildcards(callback: (project: ConfiguredProject, path: string) => void): void; + stopWatchingDirectory(): void; + close(): void; + addOpenRef(): void; + deleteOpenRef(): number; + getEffectiveTypeRoots(): string[]; + } + /** + * Project whose configuration is handled externally, such as in a '.csproj'. + * These are created only if a host explicitly calls `openExternalProject`. + */ + class ExternalProject extends Project { + externalProjectName: string; + compileOnSaveEnabled: boolean; + private readonly projectFilePath; + excludedFiles: ReadonlyArray; + private typeAcquisition; + constructor(externalProjectName: string, projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, languageServiceEnabled: boolean, compileOnSaveEnabled: boolean, projectFilePath?: string); + getExcludedFiles(): ReadonlyArray; + getProjectRootPath(): string; + getTypeAcquisition(): TypeAcquisition; + setProjectErrors(projectErrors: ReadonlyArray): void; + setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; + } +} +declare namespace ts.server { + const maxProgramSizeForNonTsFiles: number; + const ContextEvent = "context"; + const ConfigFileDiagEvent = "configFileDiag"; + const ProjectLanguageServiceStateEvent = "projectLanguageServiceState"; + const ProjectInfoTelemetryEvent = "projectInfo"; + interface ContextEvent { + eventName: typeof ContextEvent; + data: { + project: Project; + fileName: NormalizedPath; + }; + } + interface ConfigFileDiagEvent { + eventName: typeof ConfigFileDiagEvent; + data: { + triggerFile: string; + configFileName: string; + diagnostics: ReadonlyArray; + }; + } + interface ProjectLanguageServiceStateEvent { + eventName: typeof ProjectLanguageServiceStateEvent; + data: { + project: Project; + languageServiceEnabled: boolean; + }; + } + /** This will be converted to the payload of a protocol.TelemetryEvent in session.defaultEventHandler. */ + interface ProjectInfoTelemetryEvent { + readonly eventName: typeof ProjectInfoTelemetryEvent; + readonly data: ProjectInfoTelemetryEventData; + } + interface ProjectInfoTelemetryEventData { + /** Cryptographically secure hash of project file location. */ + readonly projectId: string; + /** Count of file extensions seen in the project. */ + readonly fileStats: FileStats; + /** + * Any compiler options that might contain paths will be taken out. + * Enum compiler options will be converted to strings. + */ + readonly compilerOptions: CompilerOptions; + readonly extends: boolean | undefined; + readonly files: boolean | undefined; + readonly include: boolean | undefined; + readonly exclude: boolean | undefined; + readonly compileOnSave: boolean; + readonly typeAcquisition: ProjectInfoTypeAcquisitionData; + readonly configFileName: "tsconfig.json" | "jsconfig.json" | "other"; + readonly projectType: "external" | "configured"; + readonly languageServiceEnabled: boolean; + /** TypeScript version used by the server. */ + readonly version: string; + } + interface ProjectInfoTypeAcquisitionData { + readonly enable: boolean; + readonly include: boolean; + readonly exclude: boolean; + } + interface FileStats { + readonly js: number; + readonly jsx: number; + readonly ts: number; + readonly tsx: number; + readonly dts: number; + } + type ProjectServiceEvent = ContextEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent; + interface ProjectServiceEventHandler { + (event: ProjectServiceEvent): void; + } + interface SafeList { + [name: string]: { + match: RegExp; + exclude?: (string | number)[][]; + types?: string[]; + }; + } + interface TypesMapFile { + typesMap: SafeList; + simpleMap: string[]; + } + function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings; + function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; + function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind; + function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; + /** + * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. + */ + function combineProjectOutput(projects: ReadonlyArray, action: (project: Project) => ReadonlyArray, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean): T[]; + interface HostConfiguration { + formatCodeOptions: FormatCodeSettings; + hostInfo: string; + extraFileExtensions?: JsFileExtensionInfo[]; + } + interface OpenConfiguredProjectResult { + configFileName?: NormalizedPath; + configFileErrors?: ReadonlyArray; + } + interface ProjectServiceOptions { + host: ServerHost; + logger: Logger; + cancellationToken: HostCancellationToken; + useSingleInferredProject: boolean; + useInferredProjectPerProjectRoot: boolean; + typingsInstaller: ITypingsInstaller; + eventHandler?: ProjectServiceEventHandler; + throttleWaitMilliseconds?: number; + globalPlugins?: ReadonlyArray; + pluginProbeLocations?: ReadonlyArray; + allowLocalPluginLoads?: boolean; + typesMapLocation?: string; + } + class ProjectService { + readonly typingsCache: TypingsCache; + private readonly documentRegistry; + /** + * Container of all known scripts + */ + private readonly filenameToScriptInfo; + /** + * maps external project file name to list of config files that were the part of this project + */ + private readonly externalProjectToConfiguredProjectMap; + /** + * external projects (configuration and list of root files is not controlled by tsserver) + */ + readonly externalProjects: ExternalProject[]; + /** + * projects built from openFileRoots + */ + readonly inferredProjects: InferredProject[]; + /** + * projects specified by a tsconfig.json file + */ + readonly configuredProjects: ConfiguredProject[]; + /** + * list of open files + */ + readonly openFiles: ScriptInfo[]; + private compilerOptionsForInferredProjects; + private compilerOptionsForInferredProjectsPerProjectRoot; + private readonly projectToSizeMap; + private readonly directoryWatchers; + private readonly throttledOperations; + private readonly hostConfiguration; + private safelist; + private changedFiles; + readonly toCanonicalFileName: (f: string) => string; + lastDeletedFile: ScriptInfo; + readonly host: ServerHost; + readonly logger: Logger; + readonly cancellationToken: HostCancellationToken; + readonly useSingleInferredProject: boolean; + readonly useInferredProjectPerProjectRoot: boolean; + readonly typingsInstaller: ITypingsInstaller; + readonly throttleWaitMilliseconds?: number; + private readonly eventHandler?; + readonly globalPlugins: ReadonlyArray; + readonly pluginProbeLocations: ReadonlyArray; + readonly allowLocalPluginLoads: boolean; + readonly typesMapLocation: string | undefined; + /** Tracks projects that we have already sent telemetry for. */ + private readonly seenProjects; + constructor(opts: ProjectServiceOptions); + ensureInferredProjectsUpToDate_TestOnly(): void; + getCompilerOptionsForInferredProjects(): CompilerOptions; + onUpdateLanguageServiceStateForProject(project: Project, languageServiceEnabled: boolean): void; + private loadTypesMap(); + updateTypingsForProject(response: SetTypings | InvalidateCachedTypings): void; + setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void; + stopWatchingDirectory(directory: string): void; + findProject(projectName: string): Project; + getDefaultProjectForFile(fileName: NormalizedPath, refreshInferredProjects: boolean): Project; + private ensureInferredProjectsUpToDate(); + private findContainingExternalProject(fileName); + getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings; + private updateProjectGraphs(projects); + private onSourceFileChanged(fileName); + private handleDeletedFile(info); + private onTypeRootFileChanged(project, fileName); + /** + * This is the callback function when a watched directory has added or removed source code files. + * @param project the project that associates with this directory watcher + * @param fileName the absolute file name that changed in watched directory + */ + private onSourceFileInDirectoryChangedForConfiguredProject(project, fileName); + private handleChangeInSourceFileForConfiguredProject(project, triggerFile); + private onConfigChangedForConfiguredProject(project); + /** + * This is the callback function when a watched directory has an added tsconfig file. + */ + private onConfigFileAddedForInferredProject(fileName); + private getCanonicalFileName(fileName); + private removeProject(project); + private assignScriptInfoToInferredProjectIfNecessary(info, addToListOfOpenFiles, projectRootPath?); + /** + * Remove this file from the set of open, non-configured files. + * @param info The file that has been closed or newly configured + */ + private closeOpenFile(info); + private deleteOrphanScriptInfoNotInAnyProject(); + /** + * 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, projectRootPath?); + private findConfigFile(searchPath, projectRootPath?); + private printProjects(); + private findConfiguredProjectByProjectName(configFileName); + private findExternalProjectByProjectName(projectFileName); + private convertConfigFileContentToProjectOptions(configFilename); + private exceededTotalSizeLimitForNonTsFiles(name, options, fileNames, propertyReader); + private createAndAddExternalProject(projectFileName, files, options, typeAcquisition); + private sendProjectTelemetry(projectKey, project, projectOptions?); + private reportConfigFileDiagnostics(configFileName, diagnostics, triggerFile); + private createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, clientFileName?); + private watchConfigDirectoryForProject(project, options); + private addFilesToProjectAndUpdateGraph(project, files, propertyReader, clientFileName, typeAcquisition, configFileErrors); + private openConfigFile(configFileName, clientFileName?); + private updateNonInferredProject(project, newUncheckedFiles, propertyReader, newOptions, newTypeAcquisition, compileOnSave, configFileErrors); + private updateConfiguredProject(project); + private getOrCreateInferredProjectForProjectRootPathIfEnabled(root, projectRootPath); + private getOrCreateSingleInferredProjectIfEnabled(); + private createInferredProject(isSingleInferredProject?, projectRootPath?); + createInferredProjectWithRootFileIfNecessary(root: ScriptInfo, projectRootPath?: string): InferredProject; + /** + * @param uncheckedFileName is absolute pathname + * @param fileContent is a known version of the file content that is more up to date than the one on disk + */ + getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind): ScriptInfo; + getScriptInfo(uncheckedFileName: string): ScriptInfo; + watchClosedScriptInfo(info: ScriptInfo): void; + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, isDynamic?: boolean): ScriptInfo; + getScriptInfoForNormalizedPath(fileName: NormalizedPath): ScriptInfo; + getScriptInfoForPath(fileName: Path): ScriptInfo; + setHostConfiguration(args: protocol.ConfigureRequestArguments): void; + closeLog(): void; + /** + * This function rebuilds the project for every file opened by the client + */ + reloadProjects(): void; + /** + * This function is to update the project structure for every projects. + * It is called on the premise that all the configured projects are + * up to date. + */ + refreshInferredProjects(): void; + /** + * Open file whose contents is managed by the client + * @param filename is absolute pathname + * @param fileContent is a known version of the file content that is more up to date than the one on disk + */ + openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult; + openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; + /** + * Close file whose contents is managed by the client + * @param filename is absolute pathname + */ + closeClientFile(uncheckedFileName: string): void; + private collectChanges(lastKnownProjectVersions, currentProjects, result); + private closeConfiguredProject(configFile); + closeExternalProject(uncheckedFileName: string, suppressRefresh?: boolean): void; + openExternalProjects(projects: protocol.ExternalProject[]): void; + /** Makes a filename safe to insert in a RegExp */ + private static readonly filenameEscapeRegexp; + private static escapeFilenameForRegex(filename); + resetSafeList(): void; + applySafeList(proj: protocol.ExternalProject): NormalizedPath[]; + openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects?: boolean): void; + } +} + +export = ts; +export as namespace ts; \ No newline at end of file diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts new file mode 100644 index 00000000000..ee26706ecea --- /dev/null +++ b/tests/baselines/reference/api/typescript.d.ts @@ -0,0 +1,4588 @@ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + +declare namespace ts { + /** + * Type of objects whose values are all of the same type. + * The `in` and `for-in` operators can *not* be safely used, + * since `Object.prototype` may be modified by outside code. + */ + interface MapLike { + [index: string]: T; + } + /** ES6 Map interface, only read methods included. */ + interface ReadonlyMap { + get(key: string): T | undefined; + has(key: string): boolean; + forEach(action: (value: T, key: string) => void): void; + readonly size: number; + keys(): Iterator; + values(): Iterator; + entries(): Iterator<[string, T]>; + } + /** ES6 Map interface. */ + interface Map extends ReadonlyMap { + set(key: string, value: T): this; + delete(key: string): boolean; + clear(): void; + } + /** ES6 Iterator type. */ + interface Iterator { + next(): { + value: T; + done: false; + } | { + value: never; + done: true; + }; + } + /** Array that is only intended to be pushed to, never read. */ + interface Push { + push(...values: T[]): void; + } + type Path = string & { + __pathBrand: any; + }; + interface TextRange { + pos: number; + end: number; + } + enum SyntaxKind { + Unknown = 0, + EndOfFileToken = 1, + SingleLineCommentTrivia = 2, + MultiLineCommentTrivia = 3, + NewLineTrivia = 4, + WhitespaceTrivia = 5, + ShebangTrivia = 6, + ConflictMarkerTrivia = 7, + NumericLiteral = 8, + StringLiteral = 9, + JsxText = 10, + JsxTextAllWhiteSpaces = 11, + RegularExpressionLiteral = 12, + NoSubstitutionTemplateLiteral = 13, + TemplateHead = 14, + TemplateMiddle = 15, + TemplateTail = 16, + OpenBraceToken = 17, + CloseBraceToken = 18, + OpenParenToken = 19, + CloseParenToken = 20, + OpenBracketToken = 21, + CloseBracketToken = 22, + DotToken = 23, + DotDotDotToken = 24, + SemicolonToken = 25, + CommaToken = 26, + LessThanToken = 27, + LessThanSlashToken = 28, + GreaterThanToken = 29, + LessThanEqualsToken = 30, + GreaterThanEqualsToken = 31, + EqualsEqualsToken = 32, + ExclamationEqualsToken = 33, + EqualsEqualsEqualsToken = 34, + ExclamationEqualsEqualsToken = 35, + EqualsGreaterThanToken = 36, + PlusToken = 37, + MinusToken = 38, + AsteriskToken = 39, + AsteriskAsteriskToken = 40, + SlashToken = 41, + PercentToken = 42, + PlusPlusToken = 43, + MinusMinusToken = 44, + LessThanLessThanToken = 45, + GreaterThanGreaterThanToken = 46, + GreaterThanGreaterThanGreaterThanToken = 47, + AmpersandToken = 48, + BarToken = 49, + CaretToken = 50, + ExclamationToken = 51, + TildeToken = 52, + AmpersandAmpersandToken = 53, + BarBarToken = 54, + QuestionToken = 55, + ColonToken = 56, + AtToken = 57, + EqualsToken = 58, + PlusEqualsToken = 59, + MinusEqualsToken = 60, + AsteriskEqualsToken = 61, + AsteriskAsteriskEqualsToken = 62, + SlashEqualsToken = 63, + PercentEqualsToken = 64, + LessThanLessThanEqualsToken = 65, + GreaterThanGreaterThanEqualsToken = 66, + GreaterThanGreaterThanGreaterThanEqualsToken = 67, + AmpersandEqualsToken = 68, + BarEqualsToken = 69, + CaretEqualsToken = 70, + Identifier = 71, + BreakKeyword = 72, + CaseKeyword = 73, + CatchKeyword = 74, + ClassKeyword = 75, + ConstKeyword = 76, + ContinueKeyword = 77, + DebuggerKeyword = 78, + DefaultKeyword = 79, + DeleteKeyword = 80, + DoKeyword = 81, + ElseKeyword = 82, + EnumKeyword = 83, + ExportKeyword = 84, + ExtendsKeyword = 85, + FalseKeyword = 86, + FinallyKeyword = 87, + ForKeyword = 88, + FunctionKeyword = 89, + IfKeyword = 90, + ImportKeyword = 91, + InKeyword = 92, + InstanceOfKeyword = 93, + NewKeyword = 94, + NullKeyword = 95, + ReturnKeyword = 96, + SuperKeyword = 97, + SwitchKeyword = 98, + ThisKeyword = 99, + ThrowKeyword = 100, + TrueKeyword = 101, + TryKeyword = 102, + TypeOfKeyword = 103, + VarKeyword = 104, + VoidKeyword = 105, + WhileKeyword = 106, + WithKeyword = 107, + ImplementsKeyword = 108, + InterfaceKeyword = 109, + LetKeyword = 110, + PackageKeyword = 111, + PrivateKeyword = 112, + ProtectedKeyword = 113, + PublicKeyword = 114, + StaticKeyword = 115, + YieldKeyword = 116, + AbstractKeyword = 117, + AsKeyword = 118, + AnyKeyword = 119, + AsyncKeyword = 120, + AwaitKeyword = 121, + BooleanKeyword = 122, + ConstructorKeyword = 123, + DeclareKeyword = 124, + GetKeyword = 125, + IsKeyword = 126, + KeyOfKeyword = 127, + ModuleKeyword = 128, + NamespaceKeyword = 129, + NeverKeyword = 130, + ReadonlyKeyword = 131, + RequireKeyword = 132, + NumberKeyword = 133, + ObjectKeyword = 134, + SetKeyword = 135, + StringKeyword = 136, + SymbolKeyword = 137, + TypeKeyword = 138, + UndefinedKeyword = 139, + FromKeyword = 140, + GlobalKeyword = 141, + OfKeyword = 142, + QualifiedName = 143, + ComputedPropertyName = 144, + TypeParameter = 145, + Parameter = 146, + Decorator = 147, + PropertySignature = 148, + PropertyDeclaration = 149, + MethodSignature = 150, + MethodDeclaration = 151, + Constructor = 152, + GetAccessor = 153, + SetAccessor = 154, + CallSignature = 155, + ConstructSignature = 156, + IndexSignature = 157, + TypePredicate = 158, + TypeReference = 159, + FunctionType = 160, + ConstructorType = 161, + TypeQuery = 162, + TypeLiteral = 163, + ArrayType = 164, + TupleType = 165, + UnionType = 166, + IntersectionType = 167, + ParenthesizedType = 168, + ThisType = 169, + TypeOperator = 170, + IndexedAccessType = 171, + MappedType = 172, + LiteralType = 173, + ObjectBindingPattern = 174, + ArrayBindingPattern = 175, + BindingElement = 176, + ArrayLiteralExpression = 177, + ObjectLiteralExpression = 178, + PropertyAccessExpression = 179, + ElementAccessExpression = 180, + CallExpression = 181, + NewExpression = 182, + TaggedTemplateExpression = 183, + TypeAssertionExpression = 184, + ParenthesizedExpression = 185, + FunctionExpression = 186, + ArrowFunction = 187, + DeleteExpression = 188, + TypeOfExpression = 189, + VoidExpression = 190, + AwaitExpression = 191, + PrefixUnaryExpression = 192, + PostfixUnaryExpression = 193, + BinaryExpression = 194, + ConditionalExpression = 195, + TemplateExpression = 196, + YieldExpression = 197, + SpreadElement = 198, + ClassExpression = 199, + OmittedExpression = 200, + ExpressionWithTypeArguments = 201, + AsExpression = 202, + NonNullExpression = 203, + MetaProperty = 204, + TemplateSpan = 205, + SemicolonClassElement = 206, + Block = 207, + VariableStatement = 208, + EmptyStatement = 209, + ExpressionStatement = 210, + IfStatement = 211, + DoStatement = 212, + WhileStatement = 213, + ForStatement = 214, + ForInStatement = 215, + ForOfStatement = 216, + ContinueStatement = 217, + BreakStatement = 218, + ReturnStatement = 219, + WithStatement = 220, + SwitchStatement = 221, + LabeledStatement = 222, + ThrowStatement = 223, + TryStatement = 224, + DebuggerStatement = 225, + VariableDeclaration = 226, + VariableDeclarationList = 227, + FunctionDeclaration = 228, + ClassDeclaration = 229, + InterfaceDeclaration = 230, + TypeAliasDeclaration = 231, + EnumDeclaration = 232, + ModuleDeclaration = 233, + ModuleBlock = 234, + CaseBlock = 235, + NamespaceExportDeclaration = 236, + ImportEqualsDeclaration = 237, + ImportDeclaration = 238, + ImportClause = 239, + NamespaceImport = 240, + NamedImports = 241, + ImportSpecifier = 242, + ExportAssignment = 243, + ExportDeclaration = 244, + NamedExports = 245, + ExportSpecifier = 246, + MissingDeclaration = 247, + ExternalModuleReference = 248, + JsxElement = 249, + JsxSelfClosingElement = 250, + JsxOpeningElement = 251, + JsxClosingElement = 252, + JsxAttribute = 253, + JsxAttributes = 254, + JsxSpreadAttribute = 255, + JsxExpression = 256, + CaseClause = 257, + DefaultClause = 258, + HeritageClause = 259, + CatchClause = 260, + PropertyAssignment = 261, + ShorthandPropertyAssignment = 262, + SpreadAssignment = 263, + EnumMember = 264, + SourceFile = 265, + Bundle = 266, + JSDocTypeExpression = 267, + JSDocAllType = 268, + JSDocUnknownType = 269, + JSDocNullableType = 270, + JSDocNonNullableType = 271, + JSDocOptionalType = 272, + JSDocFunctionType = 273, + JSDocVariadicType = 274, + JSDocComment = 275, + JSDocTag = 276, + JSDocAugmentsTag = 277, + JSDocClassTag = 278, + JSDocParameterTag = 279, + JSDocReturnTag = 280, + JSDocTypeTag = 281, + JSDocTemplateTag = 282, + JSDocTypedefTag = 283, + JSDocPropertyTag = 284, + JSDocTypeLiteral = 285, + SyntaxList = 286, + NotEmittedStatement = 287, + PartiallyEmittedExpression = 288, + CommaListExpression = 289, + MergeDeclarationMarker = 290, + EndOfDeclarationMarker = 291, + Count = 292, + FirstAssignment = 58, + LastAssignment = 70, + FirstCompoundAssignment = 59, + LastCompoundAssignment = 70, + FirstReservedWord = 72, + LastReservedWord = 107, + FirstKeyword = 72, + LastKeyword = 142, + FirstFutureReservedWord = 108, + LastFutureReservedWord = 116, + FirstTypeNode = 158, + LastTypeNode = 173, + FirstPunctuation = 17, + LastPunctuation = 70, + FirstToken = 0, + LastToken = 142, + FirstTriviaToken = 2, + LastTriviaToken = 7, + FirstLiteralToken = 8, + LastLiteralToken = 13, + FirstTemplateToken = 13, + LastTemplateToken = 16, + FirstBinaryOperator = 27, + LastBinaryOperator = 70, + FirstNode = 143, + FirstJSDocNode = 267, + LastJSDocNode = 285, + FirstJSDocTagNode = 276, + LastJSDocTagNode = 285, + } + enum NodeFlags { + None = 0, + Let = 1, + Const = 2, + NestedNamespace = 4, + Synthesized = 8, + Namespace = 16, + ExportContext = 32, + ContainsThis = 64, + HasImplicitReturn = 128, + HasExplicitReturn = 256, + GlobalAugmentation = 512, + HasAsyncFunctions = 1024, + DisallowInContext = 2048, + YieldContext = 4096, + DecoratorContext = 8192, + AwaitContext = 16384, + ThisNodeHasError = 32768, + JavaScriptFile = 65536, + ThisNodeOrAnySubNodesHasError = 131072, + HasAggregatedChildData = 262144, + JSDoc = 1048576, + BlockScoped = 3, + ReachabilityCheckFlags = 384, + ReachabilityAndEmitFlags = 1408, + ContextFlags = 96256, + TypeExcludesFlags = 20480, + } + enum ModifierFlags { + None = 0, + Export = 1, + Ambient = 2, + Public = 4, + Private = 8, + Protected = 16, + Static = 32, + Readonly = 64, + Abstract = 128, + Async = 256, + Default = 512, + Const = 2048, + HasComputedFlags = 536870912, + AccessibilityModifier = 28, + ParameterPropertyModifier = 92, + NonPublicAccessibilityModifier = 24, + TypeScriptModifier = 2270, + ExportDefault = 513, + } + enum JsxFlags { + None = 0, + /** An element from a named property of the JSX.IntrinsicElements interface */ + IntrinsicNamedElement = 1, + /** An element inferred from the string index signature of the JSX.IntrinsicElements interface */ + IntrinsicIndexedElement = 2, + IntrinsicElement = 3, + } + interface Node extends TextRange { + kind: SyntaxKind; + flags: NodeFlags; + decorators?: NodeArray; + modifiers?: ModifiersArray; + parent?: Node; + } + interface JSDocContainer { + } + type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | EndOfFileToken; + interface NodeArray extends ReadonlyArray, TextRange { + hasTrailingComma?: boolean; + } + interface Token extends Node { + kind: TKind; + } + type DotDotDotToken = Token; + type QuestionToken = Token; + type ColonToken = Token; + type EqualsToken = Token; + type AsteriskToken = Token; + type EqualsGreaterThanToken = Token; + type EndOfFileToken = Token & JSDocContainer; + type AtToken = Token; + type ReadonlyToken = Token; + type AwaitKeywordToken = Token; + type Modifier = Token | Token | Token | Token | Token | Token | Token | Token | Token | Token | Token; + type ModifiersArray = NodeArray; + interface Identifier extends PrimaryExpression { + kind: SyntaxKind.Identifier; + /** + * Prefer to use `id.unescapedText`. (Note: This is available only in services, not internally to the TypeScript compiler.) + * Text of identifier, but if the identifier begins with two underscores, this will begin with three. + */ + escapedText: __String; + originalKeywordKind?: SyntaxKind; + isInJSDocNamespace?: boolean; + } + interface TransientIdentifier extends Identifier { + resolvedSymbol: Symbol; + } + interface QualifiedName extends Node { + kind: SyntaxKind.QualifiedName; + left: EntityName; + right: Identifier; + } + type EntityName = Identifier | QualifiedName; + type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName; + type DeclarationName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | BindingPattern; + interface Declaration extends Node { + _declarationBrand: any; + } + interface NamedDeclaration extends Declaration { + name?: DeclarationName; + } + interface DeclarationStatement extends NamedDeclaration, Statement { + name?: Identifier | StringLiteral | NumericLiteral; + } + interface ComputedPropertyName extends Node { + kind: SyntaxKind.ComputedPropertyName; + expression: Expression; + } + interface Decorator extends Node { + kind: SyntaxKind.Decorator; + parent?: NamedDeclaration; + expression: LeftHandSideExpression; + } + interface TypeParameterDeclaration extends NamedDeclaration { + kind: SyntaxKind.TypeParameter; + parent?: DeclarationWithTypeParameters; + name: Identifier; + constraint?: TypeNode; + default?: TypeNode; + expression?: Expression; + } + interface SignatureDeclarationBase extends NamedDeclaration, JSDocContainer { + kind: SignatureDeclaration["kind"]; + name?: PropertyName; + typeParameters?: NodeArray; + parameters: NodeArray; + type: TypeNode | undefined; + } + type SignatureDeclaration = CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction; + interface CallSignatureDeclaration extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.CallSignature; + } + interface ConstructSignatureDeclaration extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.ConstructSignature; + } + type BindingName = Identifier | BindingPattern; + interface VariableDeclaration extends NamedDeclaration { + kind: SyntaxKind.VariableDeclaration; + parent?: VariableDeclarationList | CatchClause; + name: BindingName; + type?: TypeNode; + initializer?: Expression; + } + interface VariableDeclarationList extends Node { + kind: SyntaxKind.VariableDeclarationList; + parent?: VariableStatement | ForStatement | ForOfStatement | ForInStatement; + declarations: NodeArray; + } + interface ParameterDeclaration extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.Parameter; + parent?: SignatureDeclaration; + dotDotDotToken?: DotDotDotToken; + name: BindingName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface BindingElement extends NamedDeclaration { + kind: SyntaxKind.BindingElement; + parent?: BindingPattern; + propertyName?: PropertyName; + dotDotDotToken?: DotDotDotToken; + name: BindingName; + initializer?: Expression; + } + interface PropertySignature extends TypeElement, JSDocContainer { + kind: SyntaxKind.PropertySignature; + name: PropertyName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface PropertyDeclaration extends ClassElement, JSDocContainer { + kind: SyntaxKind.PropertyDeclaration; + questionToken?: QuestionToken; + name: PropertyName; + type?: TypeNode; + initializer?: Expression; + } + interface ObjectLiteralElement extends NamedDeclaration { + _objectLiteralBrandBrand: any; + name?: PropertyName; + } + type ObjectLiteralElementLike = PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | MethodDeclaration | AccessorDeclaration; + interface PropertyAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.PropertyAssignment; + name: PropertyName; + questionToken?: QuestionToken; + initializer: Expression; + } + interface ShorthandPropertyAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.ShorthandPropertyAssignment; + name: Identifier; + questionToken?: QuestionToken; + equalsToken?: Token; + objectAssignmentInitializer?: Expression; + } + interface SpreadAssignment extends ObjectLiteralElement, JSDocContainer { + parent: ObjectLiteralExpression; + kind: SyntaxKind.SpreadAssignment; + expression: Expression; + } + interface VariableLikeDeclaration extends NamedDeclaration { + propertyName?: PropertyName; + dotDotDotToken?: DotDotDotToken; + name: DeclarationName; + questionToken?: QuestionToken; + type?: TypeNode; + initializer?: Expression; + } + interface PropertyLikeDeclaration extends NamedDeclaration { + name: PropertyName; + } + interface ObjectBindingPattern extends Node { + kind: SyntaxKind.ObjectBindingPattern; + parent?: VariableDeclaration | ParameterDeclaration | BindingElement; + elements: NodeArray; + } + interface ArrayBindingPattern extends Node { + kind: SyntaxKind.ArrayBindingPattern; + parent?: VariableDeclaration | ParameterDeclaration | BindingElement; + elements: NodeArray; + } + type BindingPattern = ObjectBindingPattern | ArrayBindingPattern; + type ArrayBindingElement = BindingElement | OmittedExpression; + /** + * Several node kinds share function-like features such as a signature, + * a name, and a body. These nodes should extend FunctionLikeDeclarationBase. + * Examples: + * - FunctionDeclaration + * - MethodDeclaration + * - AccessorDeclaration + */ + interface FunctionLikeDeclarationBase extends SignatureDeclarationBase { + _functionLikeDeclarationBrand: any; + asteriskToken?: AsteriskToken; + questionToken?: QuestionToken; + body?: Block | Expression; + } + type FunctionLikeDeclaration = FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionExpression | ArrowFunction; + type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration; + interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement { + kind: SyntaxKind.FunctionDeclaration; + name?: Identifier; + body?: FunctionBody; + } + interface MethodSignature extends SignatureDeclarationBase, TypeElement { + kind: SyntaxKind.MethodSignature; + name: PropertyName; + } + interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.MethodDeclaration; + name: PropertyName; + body?: FunctionBody; + } + interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer { + kind: SyntaxKind.Constructor; + parent?: ClassDeclaration | ClassExpression; + body?: FunctionBody; + } + /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */ + interface SemicolonClassElement extends ClassElement { + kind: SyntaxKind.SemicolonClassElement; + parent?: ClassDeclaration | ClassExpression; + } + interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.GetAccessor; + parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + name: PropertyName; + body: FunctionBody; + } + interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer { + kind: SyntaxKind.SetAccessor; + parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression; + name: PropertyName; + body: FunctionBody; + } + type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration; + interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement { + kind: SyntaxKind.IndexSignature; + parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode; + } + interface TypeNode extends Node { + _typeNodeBrand: any; + } + interface KeywordTypeNode extends TypeNode { + kind: SyntaxKind.AnyKeyword | SyntaxKind.NumberKeyword | SyntaxKind.ObjectKeyword | SyntaxKind.BooleanKeyword | SyntaxKind.StringKeyword | SyntaxKind.SymbolKeyword | SyntaxKind.ThisKeyword | SyntaxKind.VoidKeyword | SyntaxKind.UndefinedKeyword | SyntaxKind.NullKeyword | SyntaxKind.NeverKeyword; + } + interface ThisTypeNode extends TypeNode { + kind: SyntaxKind.ThisType; + } + type FunctionOrConstructorTypeNode = FunctionTypeNode | ConstructorTypeNode; + interface FunctionTypeNode extends TypeNode, SignatureDeclarationBase { + kind: SyntaxKind.FunctionType; + } + interface ConstructorTypeNode extends TypeNode, SignatureDeclarationBase { + kind: SyntaxKind.ConstructorType; + } + type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments; + interface TypeReferenceNode extends TypeNode { + kind: SyntaxKind.TypeReference; + typeName: EntityName; + typeArguments?: NodeArray; + } + interface TypePredicateNode extends TypeNode { + kind: SyntaxKind.TypePredicate; + parent?: SignatureDeclaration; + parameterName: Identifier | ThisTypeNode; + type: TypeNode; + } + interface TypeQueryNode extends TypeNode { + kind: SyntaxKind.TypeQuery; + exprName: EntityName; + } + interface TypeLiteralNode extends TypeNode, Declaration { + kind: SyntaxKind.TypeLiteral; + members: NodeArray; + } + interface ArrayTypeNode extends TypeNode { + kind: SyntaxKind.ArrayType; + elementType: TypeNode; + } + interface TupleTypeNode extends TypeNode { + kind: SyntaxKind.TupleType; + elementTypes: NodeArray; + } + type UnionOrIntersectionTypeNode = UnionTypeNode | IntersectionTypeNode; + interface UnionTypeNode extends TypeNode { + kind: SyntaxKind.UnionType; + types: NodeArray; + } + interface IntersectionTypeNode extends TypeNode { + kind: SyntaxKind.IntersectionType; + types: NodeArray; + } + interface ParenthesizedTypeNode extends TypeNode { + kind: SyntaxKind.ParenthesizedType; + type: TypeNode; + } + interface TypeOperatorNode extends TypeNode { + kind: SyntaxKind.TypeOperator; + operator: SyntaxKind.KeyOfKeyword; + type: TypeNode; + } + interface IndexedAccessTypeNode extends TypeNode { + kind: SyntaxKind.IndexedAccessType; + objectType: TypeNode; + indexType: TypeNode; + } + interface MappedTypeNode extends TypeNode, Declaration { + kind: SyntaxKind.MappedType; + readonlyToken?: ReadonlyToken; + typeParameter: TypeParameterDeclaration; + questionToken?: QuestionToken; + type?: TypeNode; + } + interface LiteralTypeNode extends TypeNode { + kind: SyntaxKind.LiteralType; + literal: BooleanLiteral | LiteralExpression | PrefixUnaryExpression; + } + interface StringLiteral extends LiteralExpression { + kind: SyntaxKind.StringLiteral; + } + interface Expression extends Node { + _expressionBrand: any; + } + interface OmittedExpression extends Expression { + kind: SyntaxKind.OmittedExpression; + } + interface PartiallyEmittedExpression extends LeftHandSideExpression { + kind: SyntaxKind.PartiallyEmittedExpression; + expression: Expression; + } + interface UnaryExpression extends Expression { + _unaryExpressionBrand: any; + } + /** Deprecated, please use UpdateExpression */ + type IncrementExpression = UpdateExpression; + interface UpdateExpression extends UnaryExpression { + _updateExpressionBrand: any; + } + type PrefixUnaryOperator = SyntaxKind.PlusPlusToken | SyntaxKind.MinusMinusToken | SyntaxKind.PlusToken | SyntaxKind.MinusToken | SyntaxKind.TildeToken | SyntaxKind.ExclamationToken; + interface PrefixUnaryExpression extends UpdateExpression { + kind: SyntaxKind.PrefixUnaryExpression; + operator: PrefixUnaryOperator; + operand: UnaryExpression; + } + type PostfixUnaryOperator = SyntaxKind.PlusPlusToken | SyntaxKind.MinusMinusToken; + interface PostfixUnaryExpression extends UpdateExpression { + kind: SyntaxKind.PostfixUnaryExpression; + operand: LeftHandSideExpression; + operator: PostfixUnaryOperator; + } + interface LeftHandSideExpression extends UpdateExpression { + _leftHandSideExpressionBrand: any; + } + interface MemberExpression extends LeftHandSideExpression { + _memberExpressionBrand: any; + } + interface PrimaryExpression extends MemberExpression { + _primaryExpressionBrand: any; + } + interface NullLiteral extends PrimaryExpression, TypeNode { + kind: SyntaxKind.NullKeyword; + } + interface BooleanLiteral extends PrimaryExpression, TypeNode { + kind: SyntaxKind.TrueKeyword | SyntaxKind.FalseKeyword; + } + interface ThisExpression extends PrimaryExpression, KeywordTypeNode { + kind: SyntaxKind.ThisKeyword; + } + interface SuperExpression extends PrimaryExpression { + kind: SyntaxKind.SuperKeyword; + } + interface ImportExpression extends PrimaryExpression { + kind: SyntaxKind.ImportKeyword; + } + interface DeleteExpression extends UnaryExpression { + kind: SyntaxKind.DeleteExpression; + expression: UnaryExpression; + } + interface TypeOfExpression extends UnaryExpression { + kind: SyntaxKind.TypeOfExpression; + expression: UnaryExpression; + } + interface VoidExpression extends UnaryExpression { + kind: SyntaxKind.VoidExpression; + expression: UnaryExpression; + } + interface AwaitExpression extends UnaryExpression { + kind: SyntaxKind.AwaitExpression; + expression: UnaryExpression; + } + interface YieldExpression extends Expression { + kind: SyntaxKind.YieldExpression; + asteriskToken?: AsteriskToken; + expression?: Expression; + } + type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken; + type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken; + type MultiplicativeOperatorOrHigher = ExponentiationOperator | MultiplicativeOperator; + type AdditiveOperator = SyntaxKind.PlusToken | SyntaxKind.MinusToken; + type AdditiveOperatorOrHigher = MultiplicativeOperatorOrHigher | AdditiveOperator; + type ShiftOperator = SyntaxKind.LessThanLessThanToken | SyntaxKind.GreaterThanGreaterThanToken | SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + type ShiftOperatorOrHigher = AdditiveOperatorOrHigher | ShiftOperator; + type RelationalOperator = SyntaxKind.LessThanToken | SyntaxKind.LessThanEqualsToken | SyntaxKind.GreaterThanToken | SyntaxKind.GreaterThanEqualsToken | SyntaxKind.InstanceOfKeyword | SyntaxKind.InKeyword; + type RelationalOperatorOrHigher = ShiftOperatorOrHigher | RelationalOperator; + type EqualityOperator = SyntaxKind.EqualsEqualsToken | SyntaxKind.EqualsEqualsEqualsToken | SyntaxKind.ExclamationEqualsEqualsToken | SyntaxKind.ExclamationEqualsToken; + type EqualityOperatorOrHigher = RelationalOperatorOrHigher | EqualityOperator; + type BitwiseOperator = SyntaxKind.AmpersandToken | SyntaxKind.BarToken | SyntaxKind.CaretToken; + type BitwiseOperatorOrHigher = EqualityOperatorOrHigher | BitwiseOperator; + type LogicalOperator = SyntaxKind.AmpersandAmpersandToken | SyntaxKind.BarBarToken; + type LogicalOperatorOrHigher = BitwiseOperatorOrHigher | LogicalOperator; + type CompoundAssignmentOperator = SyntaxKind.PlusEqualsToken | SyntaxKind.MinusEqualsToken | SyntaxKind.AsteriskAsteriskEqualsToken | SyntaxKind.AsteriskEqualsToken | SyntaxKind.SlashEqualsToken | SyntaxKind.PercentEqualsToken | SyntaxKind.AmpersandEqualsToken | SyntaxKind.BarEqualsToken | SyntaxKind.CaretEqualsToken | SyntaxKind.LessThanLessThanEqualsToken | SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken | SyntaxKind.GreaterThanGreaterThanEqualsToken; + type AssignmentOperator = SyntaxKind.EqualsToken | CompoundAssignmentOperator; + type AssignmentOperatorOrHigher = LogicalOperatorOrHigher | AssignmentOperator; + type BinaryOperator = AssignmentOperatorOrHigher | SyntaxKind.CommaToken; + type BinaryOperatorToken = Token; + interface BinaryExpression extends Expression, Declaration { + kind: SyntaxKind.BinaryExpression; + left: Expression; + operatorToken: BinaryOperatorToken; + right: Expression; + } + type AssignmentOperatorToken = Token; + interface AssignmentExpression extends BinaryExpression { + left: LeftHandSideExpression; + operatorToken: TOperator; + } + interface ObjectDestructuringAssignment extends AssignmentExpression { + left: ObjectLiteralExpression; + } + interface ArrayDestructuringAssignment extends AssignmentExpression { + left: ArrayLiteralExpression; + } + type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; + type BindingOrAssignmentElement = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | OmittedExpression | SpreadElement | ArrayLiteralExpression | ObjectLiteralExpression | AssignmentExpression | Identifier | PropertyAccessExpression | ElementAccessExpression; + type BindingOrAssignmentElementRestIndicator = DotDotDotToken | SpreadElement | SpreadAssignment; + type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + type ObjectBindingOrAssignmentPattern = ObjectBindingPattern | ObjectLiteralExpression; + type ArrayBindingOrAssignmentPattern = ArrayBindingPattern | ArrayLiteralExpression; + type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression; + type BindingOrAssignmentPattern = ObjectBindingOrAssignmentPattern | ArrayBindingOrAssignmentPattern; + interface ConditionalExpression extends Expression { + kind: SyntaxKind.ConditionalExpression; + condition: Expression; + questionToken: QuestionToken; + whenTrue: Expression; + colonToken: ColonToken; + whenFalse: Expression; + } + type FunctionBody = Block; + type ConciseBody = FunctionBody | Expression; + interface FunctionExpression extends PrimaryExpression, FunctionLikeDeclarationBase, JSDocContainer { + kind: SyntaxKind.FunctionExpression; + name?: Identifier; + body: FunctionBody; + } + interface ArrowFunction extends Expression, FunctionLikeDeclarationBase, JSDocContainer { + kind: SyntaxKind.ArrowFunction; + equalsGreaterThanToken: EqualsGreaterThanToken; + body: ConciseBody; + } + interface LiteralLikeNode extends Node { + text: string; + isUnterminated?: boolean; + hasExtendedUnicodeEscape?: boolean; + } + interface LiteralExpression extends LiteralLikeNode, PrimaryExpression { + _literalExpressionBrand: any; + } + interface RegularExpressionLiteral extends LiteralExpression { + kind: SyntaxKind.RegularExpressionLiteral; + } + interface NoSubstitutionTemplateLiteral extends LiteralExpression { + kind: SyntaxKind.NoSubstitutionTemplateLiteral; + } + interface NumericLiteral extends LiteralExpression { + kind: SyntaxKind.NumericLiteral; + } + interface TemplateHead extends LiteralLikeNode { + kind: SyntaxKind.TemplateHead; + parent?: TemplateExpression; + } + interface TemplateMiddle extends LiteralLikeNode { + kind: SyntaxKind.TemplateMiddle; + parent?: TemplateSpan; + } + interface TemplateTail extends LiteralLikeNode { + kind: SyntaxKind.TemplateTail; + parent?: TemplateSpan; + } + type TemplateLiteral = TemplateExpression | NoSubstitutionTemplateLiteral; + interface TemplateExpression extends PrimaryExpression { + kind: SyntaxKind.TemplateExpression; + head: TemplateHead; + templateSpans: NodeArray; + } + interface TemplateSpan extends Node { + kind: SyntaxKind.TemplateSpan; + parent?: TemplateExpression; + expression: Expression; + literal: TemplateMiddle | TemplateTail; + } + interface ParenthesizedExpression extends PrimaryExpression, JSDocContainer { + kind: SyntaxKind.ParenthesizedExpression; + expression: Expression; + } + interface ArrayLiteralExpression extends PrimaryExpression { + kind: SyntaxKind.ArrayLiteralExpression; + elements: NodeArray; + } + interface SpreadElement extends Expression { + kind: SyntaxKind.SpreadElement; + parent?: ArrayLiteralExpression | CallExpression | NewExpression; + expression: Expression; + } + /** + * This interface is a base interface for ObjectLiteralExpression and JSXAttributes to extend from. JSXAttributes is similar to + * ObjectLiteralExpression in that it contains array of properties; however, JSXAttributes' properties can only be + * JSXAttribute or JSXSpreadAttribute. ObjectLiteralExpression, on the other hand, can only have properties of type + * ObjectLiteralElement (e.g. PropertyAssignment, ShorthandPropertyAssignment etc.) + */ + interface ObjectLiteralExpressionBase extends PrimaryExpression, Declaration { + properties: NodeArray; + } + interface ObjectLiteralExpression extends ObjectLiteralExpressionBase { + kind: SyntaxKind.ObjectLiteralExpression; + } + type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression | ParenthesizedExpression; + type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression; + interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { + kind: SyntaxKind.PropertyAccessExpression; + expression: LeftHandSideExpression; + name: Identifier; + } + interface SuperPropertyAccessExpression extends PropertyAccessExpression { + expression: SuperExpression; + } + /** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */ + interface PropertyAccessEntityNameExpression extends PropertyAccessExpression { + _propertyAccessExpressionLikeQualifiedNameBrand?: any; + expression: EntityNameExpression; + } + interface ElementAccessExpression extends MemberExpression { + kind: SyntaxKind.ElementAccessExpression; + expression: LeftHandSideExpression; + argumentExpression?: Expression; + } + interface SuperElementAccessExpression extends ElementAccessExpression { + expression: SuperExpression; + } + type SuperProperty = SuperPropertyAccessExpression | SuperElementAccessExpression; + interface CallExpression extends LeftHandSideExpression, Declaration { + kind: SyntaxKind.CallExpression; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + arguments: NodeArray; + } + interface SuperCall extends CallExpression { + expression: SuperExpression; + } + interface ImportCall extends CallExpression { + expression: ImportExpression; + } + interface ExpressionWithTypeArguments extends TypeNode { + kind: SyntaxKind.ExpressionWithTypeArguments; + parent?: HeritageClause; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + } + interface NewExpression extends PrimaryExpression, Declaration { + kind: SyntaxKind.NewExpression; + expression: LeftHandSideExpression; + typeArguments?: NodeArray; + arguments?: NodeArray; + } + interface TaggedTemplateExpression extends MemberExpression { + kind: SyntaxKind.TaggedTemplateExpression; + tag: LeftHandSideExpression; + template: TemplateLiteral; + } + type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; + interface AsExpression extends Expression { + kind: SyntaxKind.AsExpression; + expression: Expression; + type: TypeNode; + } + interface TypeAssertion extends UnaryExpression { + kind: SyntaxKind.TypeAssertionExpression; + type: TypeNode; + expression: UnaryExpression; + } + type AssertionExpression = TypeAssertion | AsExpression; + interface NonNullExpression extends LeftHandSideExpression { + kind: SyntaxKind.NonNullExpression; + expression: Expression; + } + interface MetaProperty extends PrimaryExpression { + kind: SyntaxKind.MetaProperty; + keywordToken: SyntaxKind.NewKeyword; + name: Identifier; + } + interface JsxElement extends PrimaryExpression { + kind: SyntaxKind.JsxElement; + openingElement: JsxOpeningElement; + children: NodeArray; + closingElement: JsxClosingElement; + } + type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement; + type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute; + type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression; + interface JsxAttributes extends ObjectLiteralExpressionBase { + parent?: JsxOpeningLikeElement; + } + interface JsxOpeningElement extends Expression { + kind: SyntaxKind.JsxOpeningElement; + parent?: JsxElement; + tagName: JsxTagNameExpression; + attributes: JsxAttributes; + } + interface JsxSelfClosingElement extends PrimaryExpression { + kind: SyntaxKind.JsxSelfClosingElement; + tagName: JsxTagNameExpression; + attributes: JsxAttributes; + } + interface JsxAttribute extends ObjectLiteralElement { + kind: SyntaxKind.JsxAttribute; + parent?: JsxAttributes; + name: Identifier; + initializer?: StringLiteral | JsxExpression; + } + interface JsxSpreadAttribute extends ObjectLiteralElement { + kind: SyntaxKind.JsxSpreadAttribute; + parent?: JsxAttributes; + expression: Expression; + } + interface JsxClosingElement extends Node { + kind: SyntaxKind.JsxClosingElement; + parent?: JsxElement; + tagName: JsxTagNameExpression; + } + interface JsxExpression extends Expression { + kind: SyntaxKind.JsxExpression; + parent?: JsxElement | JsxAttributeLike; + dotDotDotToken?: Token; + expression?: Expression; + } + interface JsxText extends Node { + kind: SyntaxKind.JsxText; + containsOnlyWhiteSpaces: boolean; + parent?: JsxElement; + } + type JsxChild = JsxText | JsxExpression | JsxElement | JsxSelfClosingElement; + interface Statement extends Node { + _statementBrand: any; + } + interface NotEmittedStatement extends Statement { + kind: SyntaxKind.NotEmittedStatement; + } + /** + * A list of comma-seperated expressions. This node is only created by transformations. + */ + interface CommaListExpression extends Expression { + kind: SyntaxKind.CommaListExpression; + elements: NodeArray; + } + interface EmptyStatement extends Statement { + kind: SyntaxKind.EmptyStatement; + } + interface DebuggerStatement extends Statement { + kind: SyntaxKind.DebuggerStatement; + } + interface MissingDeclaration extends DeclarationStatement, ClassElement, ObjectLiteralElement, TypeElement { + kind: SyntaxKind.MissingDeclaration; + name?: Identifier; + } + type BlockLike = SourceFile | Block | ModuleBlock | CaseOrDefaultClause; + interface Block extends Statement { + kind: SyntaxKind.Block; + statements: NodeArray; + } + interface VariableStatement extends Statement, JSDocContainer { + kind: SyntaxKind.VariableStatement; + declarationList: VariableDeclarationList; + } + interface ExpressionStatement extends Statement, JSDocContainer { + kind: SyntaxKind.ExpressionStatement; + expression: Expression; + } + interface IfStatement extends Statement { + kind: SyntaxKind.IfStatement; + expression: Expression; + thenStatement: Statement; + elseStatement?: Statement; + } + interface IterationStatement extends Statement { + statement: Statement; + } + interface DoStatement extends IterationStatement { + kind: SyntaxKind.DoStatement; + expression: Expression; + } + interface WhileStatement extends IterationStatement { + kind: SyntaxKind.WhileStatement; + expression: Expression; + } + type ForInitializer = VariableDeclarationList | Expression; + interface ForStatement extends IterationStatement { + kind: SyntaxKind.ForStatement; + initializer?: ForInitializer; + condition?: Expression; + incrementor?: Expression; + } + type ForInOrOfStatement = ForInStatement | ForOfStatement; + interface ForInStatement extends IterationStatement { + kind: SyntaxKind.ForInStatement; + initializer: ForInitializer; + expression: Expression; + } + interface ForOfStatement extends IterationStatement { + kind: SyntaxKind.ForOfStatement; + awaitModifier?: AwaitKeywordToken; + initializer: ForInitializer; + expression: Expression; + } + interface BreakStatement extends Statement { + kind: SyntaxKind.BreakStatement; + label?: Identifier; + } + interface ContinueStatement extends Statement { + kind: SyntaxKind.ContinueStatement; + label?: Identifier; + } + type BreakOrContinueStatement = BreakStatement | ContinueStatement; + interface ReturnStatement extends Statement { + kind: SyntaxKind.ReturnStatement; + expression?: Expression; + } + interface WithStatement extends Statement { + kind: SyntaxKind.WithStatement; + expression: Expression; + statement: Statement; + } + interface SwitchStatement extends Statement { + kind: SyntaxKind.SwitchStatement; + expression: Expression; + caseBlock: CaseBlock; + possiblyExhaustive?: boolean; + } + interface CaseBlock extends Node { + kind: SyntaxKind.CaseBlock; + parent?: SwitchStatement; + clauses: NodeArray; + } + interface CaseClause extends Node { + kind: SyntaxKind.CaseClause; + parent?: CaseBlock; + expression: Expression; + statements: NodeArray; + } + interface DefaultClause extends Node { + kind: SyntaxKind.DefaultClause; + parent?: CaseBlock; + statements: NodeArray; + } + type CaseOrDefaultClause = CaseClause | DefaultClause; + interface LabeledStatement extends Statement, JSDocContainer { + kind: SyntaxKind.LabeledStatement; + label: Identifier; + statement: Statement; + } + interface ThrowStatement extends Statement { + kind: SyntaxKind.ThrowStatement; + expression: Expression; + } + interface TryStatement extends Statement { + kind: SyntaxKind.TryStatement; + tryBlock: Block; + catchClause?: CatchClause; + finallyBlock?: Block; + } + interface CatchClause extends Node { + kind: SyntaxKind.CatchClause; + parent?: TryStatement; + variableDeclaration?: VariableDeclaration; + block: Block; + } + type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag; + interface ClassLikeDeclarationBase extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression; + name?: Identifier; + typeParameters?: NodeArray; + heritageClauses?: NodeArray; + members: NodeArray; + } + interface ClassDeclaration extends ClassLikeDeclarationBase, DeclarationStatement { + kind: SyntaxKind.ClassDeclaration; + name?: Identifier; + } + interface ClassExpression extends ClassLikeDeclarationBase, PrimaryExpression { + kind: SyntaxKind.ClassExpression; + } + type ClassLikeDeclaration = ClassDeclaration | ClassExpression; + interface ClassElement extends NamedDeclaration { + _classElementBrand: any; + name?: PropertyName; + } + interface TypeElement extends NamedDeclaration { + _typeElementBrand: any; + name?: PropertyName; + questionToken?: QuestionToken; + } + interface InterfaceDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.InterfaceDeclaration; + name: Identifier; + typeParameters?: NodeArray; + heritageClauses?: NodeArray; + members: NodeArray; + } + interface HeritageClause extends Node { + kind: SyntaxKind.HeritageClause; + parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression; + token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword; + types: NodeArray; + } + interface TypeAliasDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.TypeAliasDeclaration; + name: Identifier; + typeParameters?: NodeArray; + type: TypeNode; + } + interface EnumMember extends NamedDeclaration, JSDocContainer { + kind: SyntaxKind.EnumMember; + parent?: EnumDeclaration; + name: PropertyName; + initializer?: Expression; + } + interface EnumDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.EnumDeclaration; + name: Identifier; + members: NodeArray; + } + type ModuleName = Identifier | StringLiteral; + type ModuleBody = NamespaceBody | JSDocNamespaceBody; + interface ModuleDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.ModuleDeclaration; + parent?: ModuleBody | SourceFile; + name: ModuleName; + body?: ModuleBody | JSDocNamespaceDeclaration; + } + type NamespaceBody = ModuleBlock | NamespaceDeclaration; + interface NamespaceDeclaration extends ModuleDeclaration { + name: Identifier; + body: NamespaceBody; + } + type JSDocNamespaceBody = Identifier | JSDocNamespaceDeclaration; + interface JSDocNamespaceDeclaration extends ModuleDeclaration { + name: Identifier; + body: JSDocNamespaceBody; + } + interface ModuleBlock extends Node, Statement { + kind: SyntaxKind.ModuleBlock; + parent?: ModuleDeclaration; + statements: NodeArray; + } + type ModuleReference = EntityName | ExternalModuleReference; + /** + * One of: + * - import x = require("mod"); + * - import x = M.x; + */ + interface ImportEqualsDeclaration extends DeclarationStatement, JSDocContainer { + kind: SyntaxKind.ImportEqualsDeclaration; + parent?: SourceFile | ModuleBlock; + name: Identifier; + moduleReference: ModuleReference; + } + interface ExternalModuleReference extends Node { + kind: SyntaxKind.ExternalModuleReference; + parent?: ImportEqualsDeclaration; + expression?: Expression; + } + interface ImportDeclaration extends Statement { + kind: SyntaxKind.ImportDeclaration; + parent?: SourceFile | ModuleBlock; + importClause?: ImportClause; + /** If this is not a StringLiteral it will be a grammar error. */ + moduleSpecifier: Expression; + } + type NamedImportBindings = NamespaceImport | NamedImports; + interface ImportClause extends NamedDeclaration { + kind: SyntaxKind.ImportClause; + parent?: ImportDeclaration; + name?: Identifier; + namedBindings?: NamedImportBindings; + } + interface NamespaceImport extends NamedDeclaration { + kind: SyntaxKind.NamespaceImport; + parent?: ImportClause; + name: Identifier; + } + interface NamespaceExportDeclaration extends DeclarationStatement { + kind: SyntaxKind.NamespaceExportDeclaration; + name: Identifier; + } + interface ExportDeclaration extends DeclarationStatement { + kind: SyntaxKind.ExportDeclaration; + parent?: SourceFile | ModuleBlock; + exportClause?: NamedExports; + /** If this is not a StringLiteral it will be a grammar error. */ + moduleSpecifier?: Expression; + } + interface NamedImports extends Node { + kind: SyntaxKind.NamedImports; + parent?: ImportClause; + elements: NodeArray; + } + interface NamedExports extends Node { + kind: SyntaxKind.NamedExports; + parent?: ExportDeclaration; + elements: NodeArray; + } + type NamedImportsOrExports = NamedImports | NamedExports; + interface ImportSpecifier extends NamedDeclaration { + kind: SyntaxKind.ImportSpecifier; + parent?: NamedImports; + propertyName?: Identifier; + name: Identifier; + } + interface ExportSpecifier extends NamedDeclaration { + kind: SyntaxKind.ExportSpecifier; + parent?: NamedExports; + propertyName?: Identifier; + name: Identifier; + } + type ImportOrExportSpecifier = ImportSpecifier | ExportSpecifier; + interface ExportAssignment extends DeclarationStatement { + kind: SyntaxKind.ExportAssignment; + parent?: SourceFile; + isExportEquals?: boolean; + expression: Expression; + } + interface FileReference extends TextRange { + fileName: string; + } + interface CheckJsDirective extends TextRange { + enabled: boolean; + } + type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia; + interface CommentRange extends TextRange { + hasTrailingNewLine?: boolean; + kind: CommentKind; + } + interface SynthesizedComment extends CommentRange { + text: string; + pos: -1; + end: -1; + } + interface JSDocTypeExpression extends TypeNode { + kind: SyntaxKind.JSDocTypeExpression; + type: TypeNode; + } + interface JSDocType extends TypeNode { + _jsDocTypeBrand: any; + } + interface JSDocAllType extends JSDocType { + kind: SyntaxKind.JSDocAllType; + } + interface JSDocUnknownType extends JSDocType { + kind: SyntaxKind.JSDocUnknownType; + } + interface JSDocNonNullableType extends JSDocType { + kind: SyntaxKind.JSDocNonNullableType; + type: TypeNode; + } + interface JSDocNullableType extends JSDocType { + kind: SyntaxKind.JSDocNullableType; + type: TypeNode; + } + interface JSDocOptionalType extends JSDocType { + kind: SyntaxKind.JSDocOptionalType; + type: TypeNode; + } + interface JSDocFunctionType extends JSDocType, SignatureDeclarationBase { + kind: SyntaxKind.JSDocFunctionType; + } + interface JSDocVariadicType extends JSDocType { + kind: SyntaxKind.JSDocVariadicType; + type: TypeNode; + } + type JSDocTypeReferencingNode = JSDocVariadicType | JSDocOptionalType | JSDocNullableType | JSDocNonNullableType; + interface JSDoc extends Node { + kind: SyntaxKind.JSDocComment; + parent?: HasJSDoc; + tags: NodeArray | undefined; + comment: string | undefined; + } + interface JSDocTag extends Node { + parent: JSDoc; + atToken: AtToken; + tagName: Identifier; + comment: string | undefined; + } + interface JSDocUnknownTag extends JSDocTag { + kind: SyntaxKind.JSDocTag; + } + interface JSDocAugmentsTag extends JSDocTag { + kind: SyntaxKind.JSDocAugmentsTag; + class: ExpressionWithTypeArguments & { + expression: Identifier | PropertyAccessEntityNameExpression; + }; + } + interface JSDocClassTag extends JSDocTag { + kind: SyntaxKind.JSDocClassTag; + } + interface JSDocTemplateTag extends JSDocTag { + kind: SyntaxKind.JSDocTemplateTag; + typeParameters: NodeArray; + } + interface JSDocReturnTag extends JSDocTag { + kind: SyntaxKind.JSDocReturnTag; + typeExpression: JSDocTypeExpression; + } + interface JSDocTypeTag extends JSDocTag { + kind: SyntaxKind.JSDocTypeTag; + typeExpression: JSDocTypeExpression; + } + interface JSDocTypedefTag extends JSDocTag, NamedDeclaration { + parent: JSDoc; + kind: SyntaxKind.JSDocTypedefTag; + fullName?: JSDocNamespaceDeclaration | Identifier; + name?: Identifier; + typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; + } + interface JSDocPropertyLikeTag extends JSDocTag, Declaration { + parent: JSDoc; + name: EntityName; + typeExpression: JSDocTypeExpression; + /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ + isNameFirst: boolean; + isBracketed: boolean; + } + interface JSDocPropertyTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocPropertyTag; + } + interface JSDocParameterTag extends JSDocPropertyLikeTag { + kind: SyntaxKind.JSDocParameterTag; + } + interface JSDocTypeLiteral extends JSDocType { + kind: SyntaxKind.JSDocTypeLiteral; + jsDocPropertyTags?: ReadonlyArray; + /** If true, then this type literal represents an *array* of its type. */ + isArrayType?: boolean; + } + enum FlowFlags { + Unreachable = 1, + Start = 2, + BranchLabel = 4, + LoopLabel = 8, + Assignment = 16, + TrueCondition = 32, + FalseCondition = 64, + SwitchClause = 128, + ArrayMutation = 256, + Referenced = 512, + Shared = 1024, + PreFinally = 2048, + AfterFinally = 4096, + Label = 12, + Condition = 96, + } + interface FlowLock { + locked?: boolean; + } + interface AfterFinallyFlow extends FlowNodeBase, FlowLock { + antecedent: FlowNode; + } + interface PreFinallyFlow extends FlowNodeBase { + antecedent: FlowNode; + lock: FlowLock; + } + type FlowNode = AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation; + interface FlowNodeBase { + flags: FlowFlags; + id?: number; + } + interface FlowStart extends FlowNodeBase { + container?: FunctionExpression | ArrowFunction | MethodDeclaration; + } + interface FlowLabel extends FlowNodeBase { + antecedents: FlowNode[]; + } + interface FlowAssignment extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; + } + interface FlowCondition extends FlowNodeBase { + expression: Expression; + antecedent: FlowNode; + } + interface FlowSwitchClause extends FlowNodeBase { + switchStatement: SwitchStatement; + clauseStart: number; + clauseEnd: number; + antecedent: FlowNode; + } + interface FlowArrayMutation extends FlowNodeBase { + node: CallExpression | BinaryExpression; + antecedent: FlowNode; + } + type FlowType = Type | IncompleteType; + interface IncompleteType { + flags: TypeFlags; + type: Type; + } + interface AmdDependency { + path: string; + name: string; + } + interface SourceFile extends Declaration { + kind: SyntaxKind.SourceFile; + statements: NodeArray; + endOfFileToken: Token; + fileName: string; + text: string; + amdDependencies: ReadonlyArray; + moduleName: string; + referencedFiles: ReadonlyArray; + typeReferenceDirectives: ReadonlyArray; + languageVariant: LanguageVariant; + isDeclarationFile: boolean; + /** + * lib.d.ts should have a reference comment like + * + * /// + * + * If any other file has this comment, it signals not to include lib.d.ts + * because this containing file is intended to act as a default library. + */ + hasNoDefaultLib: boolean; + languageVersion: ScriptTarget; + } + interface Bundle extends Node { + kind: SyntaxKind.Bundle; + sourceFiles: ReadonlyArray; + } + interface JsonSourceFile extends SourceFile { + jsonObject?: ObjectLiteralExpression; + extendedSourceFiles?: string[]; + } + interface ScriptReferenceHost { + getCompilerOptions(): CompilerOptions; + getSourceFile(fileName: string): SourceFile; + getSourceFileByPath(path: Path): SourceFile; + getCurrentDirectory(): string; + } + interface ParseConfigHost { + useCaseSensitiveFileNames: boolean; + readDirectory(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray, includes: ReadonlyArray, depth: number): string[]; + /** + * Gets a value indicating whether the specified path exists and is a file. + * @param path The path to test. + */ + fileExists(path: string): boolean; + readFile(path: string): string | undefined; + } + interface WriteFileCallback { + (fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles: ReadonlyArray): void; + } + class OperationCanceledException { + } + interface CancellationToken { + isCancellationRequested(): boolean; + /** @throws OperationCanceledException if isCancellationRequested is true */ + throwIfCancellationRequested(): void; + } + interface Program extends ScriptReferenceHost { + /** + * Get a list of root file names that were passed to a 'createProgram' + */ + getRootFileNames(): ReadonlyArray; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. If targetSourceFile is not specified, then + * the JavaScript and declaration files will be produced for all the files in this program. + * If targetSourceFile is specified, then only the JavaScript and declaration for that + * specific file will be generated. + * + * If writeFile is not specified then the writeFile callback from the compiler host will be + * used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter + * will be invoked when writing the JavaScript and declaration files. + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Gets a type checker that can be used to semantically analyze source files in the program. + */ + getTypeChecker(): TypeChecker; + isSourceFileFromExternalLibrary(file: SourceFile): boolean; + } + interface CustomTransformers { + /** Custom transformers to evaluate before built-in transformations. */ + before?: TransformerFactory[]; + /** Custom transformers to evaluate after built-in transformations. */ + after?: TransformerFactory[]; + } + interface SourceMapSpan { + /** Line number in the .js file. */ + emittedLine: number; + /** Column number in the .js file. */ + emittedColumn: number; + /** Line number in the .ts file. */ + sourceLine: number; + /** Column number in the .ts file. */ + sourceColumn: number; + /** Optional name (index into names array) associated with this span. */ + nameIndex?: number; + /** .ts file (index into sources array) associated with this span */ + sourceIndex: number; + } + interface SourceMapData { + sourceMapFilePath: string; + jsSourceMappingURL: string; + sourceMapFile: string; + sourceMapSourceRoot: string; + sourceMapSources: string[]; + sourceMapSourcesContent?: string[]; + inputSourceFileNames: string[]; + sourceMapNames?: string[]; + sourceMapMappings: string; + sourceMapDecodedMappings: SourceMapSpan[]; + } + /** Return code used by getEmitOutput function to indicate status of the function */ + enum ExitStatus { + Success = 0, + DiagnosticsPresent_OutputsSkipped = 1, + DiagnosticsPresent_OutputsGenerated = 2, + } + interface EmitResult { + emitSkipped: boolean; + /** Contains declaration emit diagnostics */ + diagnostics: ReadonlyArray; + emittedFiles: string[]; + } + interface TypeChecker { + getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; + getDeclaredTypeOfSymbol(symbol: Symbol): Type; + getPropertiesOfType(type: Type): Symbol[]; + getPropertyOfType(type: Type, propertyName: string): Symbol | undefined; + getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo | undefined; + getSignaturesOfType(type: Type, kind: SignatureKind): Signature[]; + getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined; + getBaseTypes(type: InterfaceType): BaseType[]; + getBaseTypeOfLiteralType(type: Type): Type; + getWidenedType(type: Type): Type; + getReturnTypeOfSignature(signature: Signature): Type; + getNullableType(type: Type, flags: TypeFlags): Type; + getNonNullableType(type: Type): Type; + /** Note that the resulting nodes cannot be checked. */ + typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; + /** Note that the resulting nodes cannot be checked. */ + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; + getSymbolAtLocation(node: Node): Symbol | undefined; + getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; + getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined; + getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined; + /** + * If a symbol is a local symbol with an associated exported symbol, returns the exported symbol. + * Otherwise returns its input. + * For example, at `export type T = number;`: + * - `getSymbolAtLocation` at the location `T` will return the exported symbol for `T`. + * - But the result of `getSymbolsInScope` will contain the *local* symbol for `T`, not the exported symbol. + * - Calling `getExportSymbolOfSymbol` on that local symbol will return the exported symbol. + */ + getExportSymbolOfSymbol(symbol: Symbol): Symbol; + getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeAtLocation(node: Node): Type; + getTypeFromTypeNode(node: TypeNode): Type; + signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; + typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; + symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + getSymbolDisplayBuilder(): SymbolDisplayBuilder; + getFullyQualifiedName(symbol: Symbol): string; + getAugmentedPropertiesOfType(type: Type): Symbol[]; + getRootSymbols(symbol: Symbol): Symbol[]; + getContextualType(node: Expression): Type | undefined; + /** + * returns unknownSignature in the case of an error. + * @param argumentCount Apparent number of arguments, passed in case of a possibly incomplete call. This should come from an ArgumentListInfo. See `signatureHelp.ts`. + */ + getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[], argumentCount?: number): Signature; + getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature | undefined; + isImplementationOfOverload(node: FunctionLike): boolean | undefined; + isUndefinedSymbol(symbol: Symbol): boolean; + isArgumentsSymbol(symbol: Symbol): boolean; + isUnknownSymbol(symbol: Symbol): boolean; + getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined; + isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; + /** Follow all aliases to get the original symbol. */ + getAliasedSymbol(symbol: Symbol): Symbol; + getExportsOfModule(moduleSymbol: Symbol): Symbol[]; + getAllAttributesTypeFromJsxOpeningLikeElement(elementNode: JsxOpeningLikeElement): Type | undefined; + getJsxIntrinsicTagNames(): Symbol[]; + isOptionalParameter(node: ParameterDeclaration): boolean; + getAmbientModules(): Symbol[]; + tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; + getApparentType(type: Type): Type; + getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; + getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; + } + enum NodeBuilderFlags { + None = 0, + NoTruncation = 1, + WriteArrayAsGenericType = 2, + WriteTypeArgumentsOfSignature = 32, + UseFullyQualifiedType = 64, + SuppressAnyReturnType = 256, + WriteTypeParametersInQualifiedName = 512, + AllowThisInObjectLiteral = 1024, + AllowQualifedNameInPlaceOfIdentifier = 2048, + AllowAnonymousIdentifier = 8192, + AllowEmptyUnionOrIntersection = 16384, + AllowEmptyTuple = 32768, + IgnoreErrors = 60416, + InObjectTypeLiteral = 1048576, + InTypeAlias = 8388608, + } + interface SymbolDisplayBuilder { + buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; + buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; + buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; + buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + } + interface SymbolWriter { + writeKeyword(text: string): void; + writeOperator(text: string): void; + writePunctuation(text: string): void; + writeSpace(text: string): void; + writeStringLiteral(text: string): void; + writeParameter(text: string): void; + writeProperty(text: string): void; + writeSymbol(text: string, symbol: Symbol): void; + writeLine(): void; + increaseIndent(): void; + decreaseIndent(): void; + clear(): void; + trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError(): void; + reportPrivateInBaseOfClassExpression(propertyName: string): void; + } + enum TypeFormatFlags { + None = 0, + WriteArrayAsGenericType = 1, + UseTypeOfFunction = 4, + NoTruncation = 8, + WriteArrowStyleSignature = 16, + WriteOwnNameForAnyLike = 32, + WriteTypeArgumentsOfSignature = 64, + InElementType = 128, + UseFullyQualifiedType = 256, + InFirstTypeArgument = 512, + InTypeAlias = 1024, + SuppressAnyReturnType = 4096, + AddUndefined = 8192, + WriteClassExpressionAsTypeLiteral = 16384, + InArrayType = 32768, + UseAliasDefinedOutsideCurrentScope = 65536, + } + enum SymbolFormatFlags { + None = 0, + WriteTypeParametersOrArguments = 1, + UseOnlyExternalAliasing = 2, + } + enum TypePredicateKind { + This = 0, + Identifier = 1, + } + interface TypePredicateBase { + kind: TypePredicateKind; + type: Type; + } + interface ThisTypePredicate extends TypePredicateBase { + kind: TypePredicateKind.This; + } + interface IdentifierTypePredicate extends TypePredicateBase { + kind: TypePredicateKind.Identifier; + parameterName: string; + parameterIndex: number; + } + type TypePredicate = IdentifierTypePredicate | ThisTypePredicate; + enum SymbolFlags { + None = 0, + FunctionScopedVariable = 1, + BlockScopedVariable = 2, + Property = 4, + EnumMember = 8, + Function = 16, + Class = 32, + Interface = 64, + ConstEnum = 128, + RegularEnum = 256, + ValueModule = 512, + NamespaceModule = 1024, + TypeLiteral = 2048, + ObjectLiteral = 4096, + Method = 8192, + Constructor = 16384, + GetAccessor = 32768, + SetAccessor = 65536, + Signature = 131072, + TypeParameter = 262144, + TypeAlias = 524288, + ExportValue = 1048576, + Alias = 2097152, + Prototype = 4194304, + ExportStar = 8388608, + Optional = 16777216, + Transient = 33554432, + Enum = 384, + Variable = 3, + Value = 107455, + Type = 793064, + Namespace = 1920, + Module = 1536, + Accessor = 98304, + FunctionScopedVariableExcludes = 107454, + BlockScopedVariableExcludes = 107455, + ParameterExcludes = 107455, + PropertyExcludes = 0, + EnumMemberExcludes = 900095, + FunctionExcludes = 106927, + ClassExcludes = 899519, + InterfaceExcludes = 792968, + RegularEnumExcludes = 899327, + ConstEnumExcludes = 899967, + ValueModuleExcludes = 106639, + NamespaceModuleExcludes = 0, + MethodExcludes = 99263, + GetAccessorExcludes = 41919, + SetAccessorExcludes = 74687, + TypeParameterExcludes = 530920, + TypeAliasExcludes = 793064, + AliasExcludes = 2097152, + ModuleMember = 2623475, + ExportHasLocal = 944, + HasExports = 1952, + HasMembers = 6240, + BlockScoped = 418, + PropertyOrAccessor = 98308, + ClassMember = 106500, + } + interface Symbol { + flags: SymbolFlags; + escapedName: __String; + declarations?: Declaration[]; + valueDeclaration?: Declaration; + members?: SymbolTable; + exports?: SymbolTable; + globalExports?: SymbolTable; + } + enum InternalSymbolName { + Call = "__call", + Constructor = "__constructor", + New = "__new", + Index = "__index", + ExportStar = "__export", + Global = "__global", + Missing = "__missing", + Type = "__type", + Object = "__object", + JSXAttributes = "__jsxAttributes", + Class = "__class", + Function = "__function", + Computed = "__computed", + Resolving = "__resolving__", + ExportEquals = "export=", + Default = "default", + } + /** + * This represents a string whose leading underscore have been escaped by adding extra leading underscores. + * The shape of this brand is rather unique compared to others we've used. + * Instead of just an intersection of a string and an object, it is that union-ed + * with an intersection of void and an object. This makes it wholly incompatible + * with a normal string (which is good, it cannot be misused on assignment or on usage), + * while still being comparable with a normal string via === (also good) and castable from a string. + */ + type __String = (string & { + __escapedIdentifier: void; + }) | (void & { + __escapedIdentifier: void; + }) | InternalSymbolName; + /** ReadonlyMap where keys are `__String`s. */ + interface ReadonlyUnderscoreEscapedMap { + get(key: __String): T | undefined; + has(key: __String): boolean; + forEach(action: (value: T, key: __String) => void): void; + readonly size: number; + keys(): Iterator<__String>; + values(): Iterator; + entries(): Iterator<[__String, T]>; + } + /** Map where keys are `__String`s. */ + interface UnderscoreEscapedMap extends ReadonlyUnderscoreEscapedMap { + set(key: __String, value: T): this; + delete(key: __String): boolean; + clear(): void; + } + /** SymbolTable based on ES6 Map interface. */ + type SymbolTable = UnderscoreEscapedMap; + enum TypeFlags { + Any = 1, + String = 2, + Number = 4, + Boolean = 8, + Enum = 16, + StringLiteral = 32, + NumberLiteral = 64, + BooleanLiteral = 128, + EnumLiteral = 256, + ESSymbol = 512, + Void = 1024, + Undefined = 2048, + Null = 4096, + Never = 8192, + TypeParameter = 16384, + Object = 32768, + Union = 65536, + Intersection = 131072, + Index = 262144, + IndexedAccess = 524288, + NonPrimitive = 16777216, + MarkerType = 67108864, + Literal = 224, + Unit = 6368, + StringOrNumberLiteral = 96, + PossiblyFalsy = 7406, + StringLike = 262178, + NumberLike = 84, + BooleanLike = 136, + EnumLike = 272, + UnionOrIntersection = 196608, + StructuredType = 229376, + StructuredOrTypeVariable = 1032192, + TypeVariable = 540672, + Narrowable = 17810175, + NotUnionOrUnit = 16810497, + } + type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; + interface Type { + flags: TypeFlags; + symbol?: Symbol; + pattern?: DestructuringPattern; + aliasSymbol?: Symbol; + aliasTypeArguments?: Type[]; + } + interface LiteralType extends Type { + value: string | number; + freshType?: LiteralType; + regularType?: LiteralType; + } + interface StringLiteralType extends LiteralType { + value: string; + } + interface NumberLiteralType extends LiteralType { + value: number; + } + interface EnumType extends Type { + } + enum ObjectFlags { + Class = 1, + Interface = 2, + Reference = 4, + Tuple = 8, + Anonymous = 16, + Mapped = 32, + Instantiated = 64, + ObjectLiteral = 128, + EvolvingArray = 256, + ObjectLiteralPatternWithComputedProperties = 512, + ClassOrInterface = 3, + } + interface ObjectType extends Type { + objectFlags: ObjectFlags; + } + /** Class and interface types (ObjectFlags.Class and ObjectFlags.Interface). */ + interface InterfaceType extends ObjectType { + typeParameters: TypeParameter[]; + outerTypeParameters: TypeParameter[]; + localTypeParameters: TypeParameter[]; + thisType: TypeParameter; + } + type BaseType = ObjectType | IntersectionType; + interface InterfaceTypeWithDeclaredMembers extends InterfaceType { + declaredProperties: Symbol[]; + declaredCallSignatures: Signature[]; + declaredConstructSignatures: Signature[]; + declaredStringIndexInfo: IndexInfo; + declaredNumberIndexInfo: IndexInfo; + } + /** + * Type references (ObjectFlags.Reference). When a class or interface has type parameters or + * a "this" type, references to the class or interface are made using type references. The + * typeArguments property specifies the types to substitute for the type parameters of the + * class or interface and optionally includes an extra element that specifies the type to + * substitute for "this" in the resulting instantiation. When no extra argument is present, + * the type reference itself is substituted for "this". The typeArguments property is undefined + * if the class or interface has no type parameters and the reference isn't specifying an + * explicit "this" argument. + */ + interface TypeReference extends ObjectType { + target: GenericType; + typeArguments?: Type[]; + } + interface GenericType extends InterfaceType, TypeReference { + } + interface UnionOrIntersectionType extends Type { + types: Type[]; + } + interface UnionType extends UnionOrIntersectionType { + } + interface IntersectionType extends UnionOrIntersectionType { + } + type StructuredType = ObjectType | UnionType | IntersectionType; + interface EvolvingArrayType extends ObjectType { + elementType: Type; + finalArrayType?: Type; + } + interface TypeVariable extends Type { + } + interface TypeParameter extends TypeVariable { + /** Retrieve using getConstraintFromTypeParameter */ + constraint: Type; + default?: Type; + } + interface IndexedAccessType extends TypeVariable { + objectType: Type; + indexType: Type; + constraint?: Type; + } + interface IndexType extends Type { + type: TypeVariable | UnionOrIntersectionType; + } + enum SignatureKind { + Call = 0, + Construct = 1, + } + interface Signature { + declaration: SignatureDeclaration; + typeParameters?: TypeParameter[]; + parameters: Symbol[]; + } + enum IndexKind { + String = 0, + Number = 1, + } + interface IndexInfo { + type: Type; + isReadonly: boolean; + declaration?: SignatureDeclaration; + } + enum InferencePriority { + Contravariant = 1, + NakedTypeVariable = 2, + MappedType = 4, + ReturnType = 8, + } + interface InferenceInfo { + typeParameter: TypeParameter; + candidates: Type[]; + inferredType: Type; + priority: InferencePriority; + topLevel: boolean; + isFixed: boolean; + } + enum InferenceFlags { + InferUnionTypes = 1, + NoDefault = 2, + AnyDefault = 4, + } + /** + * Ternary values are defined such that + * x & y is False if either x or y is False. + * x & y is Maybe if either x or y is Maybe, but neither x or y is False. + * x & y is True if both x and y are True. + * x | y is False if both x and y are False. + * x | y is Maybe if either x or y is Maybe, but neither x or y is True. + * x | y is True if either x or y is True. + */ + enum Ternary { + False = 0, + Maybe = 1, + True = -1, + } + type TypeComparer = (s: Type, t: Type, reportErrors?: boolean) => Ternary; + interface JsFileExtensionInfo { + extension: string; + isMixedContent: boolean; + } + interface DiagnosticMessage { + key: string; + category: DiagnosticCategory; + code: number; + message: string; + } + /** + * A linked list of formatted diagnostic messages to be used as part of a multiline message. + * It is built from the bottom up, leaving the head to be the "main" diagnostic. + * While it seems that DiagnosticMessageChain is structurally similar to DiagnosticMessage, + * the difference is that messages are all preformatted in DMC. + */ + interface DiagnosticMessageChain { + messageText: string; + category: DiagnosticCategory; + code: number; + next?: DiagnosticMessageChain; + } + interface Diagnostic { + file: SourceFile | undefined; + start: number | undefined; + length: number | undefined; + messageText: string | DiagnosticMessageChain; + category: DiagnosticCategory; + code: number; + source?: string; + } + enum DiagnosticCategory { + Warning = 0, + Error = 1, + Message = 2, + } + enum ModuleResolutionKind { + Classic = 1, + NodeJs = 2, + } + interface PluginImport { + name: string; + } + type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike | PluginImport[] | null | undefined; + interface CompilerOptions { + allowJs?: boolean; + allowSyntheticDefaultImports?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + alwaysStrict?: boolean; + baseUrl?: string; + charset?: string; + checkJs?: boolean; + declaration?: boolean; + declarationDir?: string; + disableSizeLimit?: boolean; + downlevelIteration?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + importHelpers?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + isolatedModules?: boolean; + jsx?: JsxEmit; + lib?: string[]; + locale?: string; + mapRoot?: string; + maxNodeModuleJsDepth?: number; + module?: ModuleKind; + moduleResolution?: ModuleResolutionKind; + newLine?: NewLineKind; + noEmit?: boolean; + noEmitHelpers?: boolean; + noEmitOnError?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitReturns?: boolean; + noImplicitThis?: boolean; + noStrictGenericChecks?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noImplicitUseStrict?: boolean; + noLib?: boolean; + noResolve?: boolean; + out?: string; + outDir?: string; + outFile?: string; + paths?: MapLike; + preserveConstEnums?: boolean; + preserveSymlinks?: boolean; + project?: string; + reactNamespace?: string; + jsxFactory?: string; + removeComments?: boolean; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + strict?: boolean; + strictFunctionTypes?: boolean; + strictNullChecks?: boolean; + suppressExcessPropertyErrors?: boolean; + suppressImplicitAnyIndexErrors?: boolean; + target?: ScriptTarget; + traceResolution?: boolean; + types?: string[]; + /** Paths used to compute primary types search locations */ + typeRoots?: string[]; + [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; + } + interface TypeAcquisition { + enableAutoDiscovery?: boolean; + enable?: boolean; + include?: string[]; + exclude?: string[]; + [option: string]: string[] | boolean | undefined; + } + interface DiscoverTypingsInfo { + fileNames: string[]; + projectRootPath: string; + safeListPath: string; + packageNameToTypingLocation: Map; + typeAcquisition: TypeAcquisition; + compilerOptions: CompilerOptions; + unresolvedImports: ReadonlyArray; + } + enum ModuleKind { + None = 0, + CommonJS = 1, + AMD = 2, + UMD = 3, + System = 4, + ES2015 = 5, + ESNext = 6, + } + enum JsxEmit { + None = 0, + Preserve = 1, + React = 2, + ReactNative = 3, + } + enum NewLineKind { + CarriageReturnLineFeed = 0, + LineFeed = 1, + } + interface LineAndCharacter { + line: number; + character: number; + } + enum ScriptKind { + Unknown = 0, + JS = 1, + JSX = 2, + TS = 3, + TSX = 4, + External = 5, + JSON = 6, + } + enum ScriptTarget { + ES3 = 0, + ES5 = 1, + ES2015 = 2, + ES2016 = 3, + ES2017 = 4, + ESNext = 5, + Latest = 5, + } + enum LanguageVariant { + Standard = 0, + JSX = 1, + } + /** Either a parsed command line or a parsed tsconfig.json */ + interface ParsedCommandLine { + options: CompilerOptions; + typeAcquisition?: TypeAcquisition; + fileNames: string[]; + raw?: any; + errors: Diagnostic[]; + wildcardDirectories?: MapLike; + compileOnSave?: boolean; + } + enum WatchDirectoryFlags { + None = 0, + Recursive = 1, + } + interface ExpandResult { + fileNames: string[]; + wildcardDirectories: MapLike; + } + interface ModuleResolutionHost { + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + trace?(s: string): void; + directoryExists?(directoryName: string): boolean; + /** + * Resolve a symbolic link. + * @see https://nodejs.org/api/fs.html#fs_fs_realpathsync_path_options + */ + realpath?(path: string): string; + getCurrentDirectory?(): string; + getDirectories?(path: string): string[]; + } + /** + * Represents the result of module resolution. + * Module resolution will pick up tsx/jsx/js files even if '--jsx' and '--allowJs' are turned off. + * The Program will then filter results based on these flags. + * + * Prefer to return a `ResolvedModuleFull` so that the file type does not have to be inferred. + */ + interface ResolvedModule { + /** Path of the file the module was resolved to. */ + resolvedFileName: string; + /** True if `resolvedFileName` comes from `node_modules`. */ + isExternalLibraryImport?: boolean; + } + /** + * ResolvedModule with an explicitly provided `extension` property. + * Prefer this over `ResolvedModule`. + * If changing this, remember to change `moduleResolutionIsEqualTo`. + */ + interface ResolvedModuleFull extends ResolvedModule { + /** + * Extension of resolvedFileName. This must match what's at the end of resolvedFileName. + * This is optional for backwards-compatibility, but will be added if not provided. + */ + extension: Extension; + packageId?: PackageId; + } + /** + * Unique identifier with a package name and version. + * If changing this, remember to change `packageIdIsEqual`. + */ + interface PackageId { + /** + * Name of the package. + * Should not include `@types`. + * If accessing a non-index file, this should include its name e.g. "foo/bar". + */ + name: string; + /** + * Name of a submodule within this package. + * May be "". + */ + subModuleName: string; + /** Version of the package, e.g. "1.2.3" */ + version: string; + } + enum Extension { + Ts = ".ts", + Tsx = ".tsx", + Dts = ".d.ts", + Js = ".js", + Jsx = ".jsx", + } + interface ResolvedModuleWithFailedLookupLocations { + resolvedModule: ResolvedModuleFull | undefined; + } + interface ResolvedTypeReferenceDirective { + primary: boolean; + resolvedFileName?: string; + packageId?: PackageId; + } + interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective; + failedLookupLocations: string[]; + } + interface CompilerHost extends ModuleResolutionHost { + getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; + getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; + getCancellationToken?(): CancellationToken; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; + writeFile: WriteFileCallback; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + getCanonicalFileName(fileName: string): string; + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + /** + * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files + */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + getEnvironmentVariable?(name: string): string; + } + interface SourceMapRange extends TextRange { + source?: SourceMapSource; + } + interface SourceMapSource { + fileName: string; + text: string; + skipTrivia?: (pos: number) => number; + } + enum EmitFlags { + SingleLine = 1, + AdviseOnEmitNode = 2, + NoSubstitution = 4, + CapturesThis = 8, + NoLeadingSourceMap = 16, + NoTrailingSourceMap = 32, + NoSourceMap = 48, + NoNestedSourceMaps = 64, + NoTokenLeadingSourceMaps = 128, + NoTokenTrailingSourceMaps = 256, + NoTokenSourceMaps = 384, + NoLeadingComments = 512, + NoTrailingComments = 1024, + NoComments = 1536, + NoNestedComments = 2048, + HelperName = 4096, + ExportName = 8192, + LocalName = 16384, + InternalName = 32768, + Indented = 65536, + NoIndentation = 131072, + AsyncFunctionBody = 262144, + ReuseTempVariableScope = 524288, + CustomPrologue = 1048576, + NoHoisting = 2097152, + HasEndOfDeclarationMarker = 4194304, + Iterator = 8388608, + NoAsciiEscaping = 16777216, + } + interface EmitHelper { + readonly name: string; + readonly scoped: boolean; + readonly text: string; + readonly priority?: number; + } + enum EmitHint { + SourceFile = 0, + Expression = 1, + IdentifierName = 2, + MappedTypeParameter = 3, + Unspecified = 4, + } + interface TransformationContext { + /** Gets the compiler options supplied to the transformer. */ + getCompilerOptions(): CompilerOptions; + /** Starts a new lexical environment. */ + startLexicalEnvironment(): void; + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + suspendLexicalEnvironment(): void; + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + resumeLexicalEnvironment(): void; + /** Ends a lexical environment, returning any declarations. */ + endLexicalEnvironment(): Statement[]; + /** Hoists a function declaration to the containing scope. */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + /** Hoists a variable declaration to the containing scope. */ + hoistVariableDeclaration(node: Identifier): void; + /** Records a request for a non-scoped emit helper in the current context. */ + requestEmitHelper(helper: EmitHelper): void; + /** Gets and resets the requested non-scoped emit helpers. */ + readEmitHelpers(): EmitHelper[] | undefined; + /** Enables expression substitutions in the pretty printer for the provided SyntaxKind. */ + enableSubstitution(kind: SyntaxKind): void; + /** Determines whether expression substitutions are enabled for the provided node. */ + isSubstitutionEnabled(node: Node): boolean; + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `NodeTransformer` callback. + */ + onSubstituteNode: (hint: EmitHint, node: Node) => Node; + /** + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + /** + * Hook used to allow transformers to capture state before or after + * the printer emits a node. + * + * NOTE: Transformation hooks should only be modified during `Transformer` initialization, + * before returning the `NodeTransformer` callback. + */ + onEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; + } + interface TransformationResult { + /** Gets the transformed source files. */ + transformed: T[]; + /** Gets diagnostics for the transformation. */ + diagnostics?: Diagnostic[]; + /** + * Gets a substitute for a node, if one is available; otherwise, returns the original node. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to substitute. + */ + substituteNode(hint: EmitHint, node: Node): Node; + /** + * Emits a node with possible notification. + * + * @param hint A hint as to the intended usage of the node. + * @param node The node to emit. + * @param emitCallback A callback used to emit the node. + */ + emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + /** + * Clean up EmitNode entries on any parse-tree nodes. + */ + dispose(): void; + } + /** + * A function that is used to initialize and return a `Transformer` callback, which in turn + * will be used to transform one or more nodes. + */ + type TransformerFactory = (context: TransformationContext) => Transformer; + /** + * A function that transforms a node. + */ + type Transformer = (node: T) => T; + /** + * A function that accepts and possibly transforms a node. + */ + type Visitor = (node: Node) => VisitResult; + type VisitResult = T | T[] | undefined; + interface Printer { + /** + * Print a node and its subtree as-is, without any emit transformations. + * @param hint A value indicating the purpose of a node. This is primarily used to + * distinguish between an `Identifier` used in an expression position, versus an + * `Identifier` used as an `IdentifierName` as part of a declaration. For most nodes you + * should just pass `Unspecified`. + * @param node The node to print. The node and its subtree are printed as-is, without any + * emit transformations. + * @param sourceFile A source file that provides context for the node. The source text of + * the file is used to emit the original source content for literals and identifiers, while + * the identifiers of the source file are used when generating unique names to avoid + * collisions. + */ + printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string; + /** + * Prints a source file as-is, without any emit transformations. + */ + printFile(sourceFile: SourceFile): string; + /** + * Prints a bundle of source files as-is, without any emit transformations. + */ + printBundle(bundle: Bundle): string; + } + interface PrintHandlers { + /** + * A hook used by the Printer when generating unique names to avoid collisions with + * globally defined names that exist outside of the current source file. + */ + hasGlobalName?(name: string): boolean; + /** + * A hook used by the Printer to provide notifications prior to emitting a node. A + * compatible implementation **must** invoke `emitCallback` with the provided `hint` and + * `node` values. + * @param hint A hint indicating the intended purpose of the node. + * @param node The node to emit. + * @param emitCallback A callback that, when invoked, will emit the node. + * @example + * ```ts + * var printer = createPrinter(printerOptions, { + * onEmitNode(hint, node, emitCallback) { + * // set up or track state prior to emitting the node... + * emitCallback(hint, node); + * // restore state after emitting the node... + * } + * }); + * ``` + */ + onEmitNode?(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; + /** + * A hook used by the Printer to perform just-in-time substitution of a node. This is + * primarily used by node transformations that need to substitute one node for another, + * such as replacing `myExportedVar` with `exports.myExportedVar`. + * @param hint A hint indicating the intended purpose of the node. + * @param node The node to emit. + * @example + * ```ts + * var printer = createPrinter(printerOptions, { + * substituteNode(hint, node) { + * // perform substitution if necessary... + * return node; + * } + * }); + * ``` + */ + substituteNode?(hint: EmitHint, node: Node): Node; + } + interface PrinterOptions { + removeComments?: boolean; + newLine?: NewLineKind; + } + interface TextSpan { + start: number; + length: number; + } + interface TextChangeRange { + span: TextSpan; + newLength: number; + } + interface SyntaxList extends Node { + _children: Node[]; + } +} +declare namespace ts { + const versionMajorMinor = "2.6"; + /** The version of the TypeScript compiler release */ + const version: string; +} +declare namespace ts { + function isExternalModuleNameRelative(moduleName: string): boolean; +} +declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; +declare function clearTimeout(handle: any): void; +declare namespace ts { + enum FileWatcherEventKind { + Created = 0, + Changed = 1, + Deleted = 2, + } + type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void; + type DirectoryWatcherCallback = (fileName: string) => void; + interface WatchedFile { + fileName: string; + callback: FileWatcherCallback; + mtime?: Date; + } + interface System { + args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; + getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + /** + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching + */ + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; + getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + getModifiedTime?(path: string): Date; + /** + * This should be cryptographically secure. + * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) + */ + createHash?(data: string): string; + getMemoryUsage?(): number; + exit(exitCode?: number): void; + realpath?(path: string): string; + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + clearTimeout?(timeoutId: any): void; + } + interface FileWatcher { + close(): void; + } + interface DirectoryWatcher extends FileWatcher { + directoryName: string; + referenceCount: number; + } + function getNodeMajorVersion(): number; + let sys: System; +} +declare namespace ts { + interface ErrorCallback { + (message: DiagnosticMessage, length: number): void; + } + interface Scanner { + getStartPos(): number; + getToken(): SyntaxKind; + getTextPos(): number; + getTokenPos(): number; + getTokenText(): string; + getTokenValue(): string; + hasExtendedUnicodeEscape(): boolean; + hasPrecedingLineBreak(): boolean; + isIdentifier(): boolean; + isReservedWord(): boolean; + isUnterminated(): boolean; + reScanGreaterToken(): SyntaxKind; + reScanSlashToken(): SyntaxKind; + reScanTemplateToken(): SyntaxKind; + scanJsxIdentifier(): SyntaxKind; + scanJsxAttributeValue(): SyntaxKind; + reScanJsxToken(): SyntaxKind; + scanJsxToken(): SyntaxKind; + scanJSDocToken(): SyntaxKind; + scan(): SyntaxKind; + getText(): string; + setText(text: string, start?: number, length?: number): void; + setOnError(onError: ErrorCallback): void; + setScriptTarget(scriptTarget: ScriptTarget): void; + setLanguageVariant(variant: LanguageVariant): void; + setTextPos(textPos: number): void; + lookAhead(callback: () => T): T; + scanRange(start: number, length: number, callback: () => T): T; + tryScan(callback: () => T): T; + } + function tokenToString(t: SyntaxKind): string | undefined; + function getPositionOfLineAndCharacter(sourceFile: SourceFile, line: number, character: number): number; + function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter; + function isWhiteSpaceLike(ch: number): boolean; + /** Does not include line breaks. For that, see isWhiteSpaceLike. */ + function isWhiteSpaceSingleLine(ch: number): boolean; + function isLineBreak(ch: number): boolean; + function couldStartTrivia(text: string, pos: number): boolean; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; + function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; + function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined; + function getTrailingCommentRanges(text: string, pos: number): CommentRange[] | undefined; + /** Optionally, get the shebang */ + function getShebang(text: string): string | undefined; + function isIdentifierStart(ch: number, languageVersion: ScriptTarget): boolean; + function isIdentifierPart(ch: number, languageVersion: ScriptTarget): boolean; + function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, languageVariant?: LanguageVariant, text?: string, onError?: ErrorCallback, start?: number, length?: number): Scanner; +} +declare namespace ts { + function getDefaultLibFileName(options: CompilerOptions): string; + function textSpanEnd(span: TextSpan): number; + function textSpanIsEmpty(span: TextSpan): boolean; + function textSpanContainsPosition(span: TextSpan, position: number): boolean; + function textSpanContainsTextSpan(span: TextSpan, other: TextSpan): boolean; + function textSpanOverlapsWith(span: TextSpan, other: TextSpan): boolean; + function textSpanOverlap(span1: TextSpan, span2: TextSpan): TextSpan; + function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan): boolean; + function textSpanIntersectsWith(span: TextSpan, start: number, length: number): boolean; + function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number): boolean; + function textSpanIntersectsWithPosition(span: TextSpan, position: number): boolean; + function textSpanIntersection(span1: TextSpan, span2: TextSpan): TextSpan; + function createTextSpan(start: number, length: number): TextSpan; + function createTextSpanFromBounds(start: number, end: number): TextSpan; + function textChangeRangeNewSpan(range: TextChangeRange): TextSpan; + function textChangeRangeIsUnchanged(range: TextChangeRange): boolean; + function createTextChangeRange(span: TextSpan, newLength: number): TextChangeRange; + let unchangedTextChangeRange: TextChangeRange; + /** + * Called to merge all the changes that occurred across several versions of a script snapshot + * into a single change. i.e. if a user keeps making successive edits to a script we will + * have a text change from V1 to V2, V2 to V3, ..., Vn. + * + * This function will then merge those changes into a single change range valid between V1 and + * Vn. + */ + function collapseTextChangeRangesAcrossMultipleVersions(changes: ReadonlyArray): TextChangeRange; + function getTypeParameterOwner(d: Declaration): Declaration; + function isParameterPropertyDeclaration(node: Node): boolean; + function isEmptyBindingPattern(node: BindingName): node is BindingPattern; + function isEmptyBindingElement(node: BindingElement): boolean; + function getCombinedModifierFlags(node: Node): ModifierFlags; + function getCombinedNodeFlags(node: Node): NodeFlags; + /** + * Checks to see if the locale is in the appropriate format, + * and if it is, attempts to set the appropriate language. + */ + function validateLocaleAndSetLanguage(locale: string, sys: { + getExecutingFilePath(): string; + resolvePath(path: string): string; + fileExists(fileName: string): boolean; + readFile(fileName: string): string | undefined; + }, errors?: Push): void; + function getOriginalNode(node: Node): Node; + function getOriginalNode(node: Node, nodeTest: (node: Node) => node is T): T; + /** + * Gets a value indicating whether a node originated in the parse tree. + * + * @param node The node to test. + */ + function isParseTreeNode(node: Node): boolean; + /** + * Gets the original parse tree node for a node. + * + * @param node The original node. + * @returns The original parse tree node if found; otherwise, undefined. + */ + function getParseTreeNode(node: Node): Node; + /** + * Gets the original parse tree node for a node. + * + * @param node The original node. + * @param nodeTest A callback used to ensure the correct type of parse tree node is returned. + * @returns The original parse tree node if found; otherwise, undefined. + */ + function getParseTreeNode(node: Node, nodeTest?: (node: Node) => node is T): T; + /** + * Remove extra underscore from escaped identifier text content. + * + * @param identifier The escaped identifier text. + * @returns The unescaped identifier text. + */ + function unescapeLeadingUnderscores(identifier: __String): string; + function idText(identifier: Identifier): string; + function symbolName(symbol: Symbol): string; + /** + * Remove extra underscore from escaped identifier text content. + * @deprecated Use `id.text` for the unescaped text. + * @param identifier The escaped identifier text. + * @returns The unescaped identifier text. + */ + function unescapeIdentifier(id: string): string; + function getNameOfJSDocTypedef(declaration: JSDocTypedefTag): Identifier | undefined; + function getNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined; + /** + * Gets the JSDoc parameter tags for the node if present. + * + * @remarks Returns any JSDoc param tag that matches the provided + * parameter, whether a param tag on a containing function + * expression, or a param tag on a variable declaration whose + * initializer is the containing function. The tags closest to the + * node are returned first, so in the previous example, the param + * tag on the containing function expression would be first. + * + * Does not return tags for binding patterns, because JSDoc matches + * parameters by name and binding patterns do not have a name. + */ + function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray | undefined; + /** + * Return true if the node has JSDoc parameter tags. + * + * @remarks Includes parameter tags that are not directly on the node, + * for example on a variable declaration whose initializer is a function expression. + */ + function hasJSDocParameterTags(node: FunctionLikeDeclaration | SignatureDeclaration): boolean; + /** Gets the JSDoc augments tag for the node if present */ + function getJSDocAugmentsTag(node: Node): JSDocAugmentsTag | undefined; + /** Gets the JSDoc class tag for the node if present */ + function getJSDocClassTag(node: Node): JSDocClassTag | undefined; + /** Gets the JSDoc return tag for the node if present */ + function getJSDocReturnTag(node: Node): JSDocReturnTag | undefined; + /** Gets the JSDoc template tag for the node if present */ + function getJSDocTemplateTag(node: Node): JSDocTemplateTag | undefined; + /** Gets the JSDoc type tag for the node if present and valid */ + function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined; + /** + * Gets the type node for the node if provided via JSDoc. + * + * @remarks The search includes any JSDoc param tag that relates + * to the provided parameter, for example a type tag on the + * parameter itself, or a param tag on a containing function + * expression, or a param tag on a variable declaration whose + * initializer is the containing function. The tags closest to the + * node are examined first, so in the previous example, the type + * tag directly on the node would be returned. + */ + function getJSDocType(node: Node): TypeNode | undefined; + /** + * Gets the return type node for the node if provided via JSDoc's return tag. + * + * @remarks `getJSDocReturnTag` just gets the whole JSDoc tag. This function + * gets the type from inside the braces. + */ + function getJSDocReturnType(node: Node): TypeNode | undefined; + /** Get all JSDoc tags related to a node, including those on parent nodes. */ + function getJSDocTags(node: Node): ReadonlyArray | undefined; +} +declare namespace ts { + function isNumericLiteral(node: Node): node is NumericLiteral; + function isStringLiteral(node: Node): node is StringLiteral; + function isJsxText(node: Node): node is JsxText; + function isRegularExpressionLiteral(node: Node): node is RegularExpressionLiteral; + function isNoSubstitutionTemplateLiteral(node: Node): node is LiteralExpression; + function isTemplateHead(node: Node): node is TemplateHead; + function isTemplateMiddle(node: Node): node is TemplateMiddle; + function isTemplateTail(node: Node): node is TemplateTail; + function isIdentifier(node: Node): node is Identifier; + function isQualifiedName(node: Node): node is QualifiedName; + function isComputedPropertyName(node: Node): node is ComputedPropertyName; + function isTypeParameterDeclaration(node: Node): node is TypeParameterDeclaration; + function isParameter(node: Node): node is ParameterDeclaration; + function isDecorator(node: Node): node is Decorator; + function isPropertySignature(node: Node): node is PropertySignature; + function isPropertyDeclaration(node: Node): node is PropertyDeclaration; + function isMethodSignature(node: Node): node is MethodSignature; + function isMethodDeclaration(node: Node): node is MethodDeclaration; + function isConstructorDeclaration(node: Node): node is ConstructorDeclaration; + function isGetAccessorDeclaration(node: Node): node is GetAccessorDeclaration; + function isSetAccessorDeclaration(node: Node): node is SetAccessorDeclaration; + function isCallSignatureDeclaration(node: Node): node is CallSignatureDeclaration; + function isConstructSignatureDeclaration(node: Node): node is ConstructSignatureDeclaration; + function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration; + function isTypePredicateNode(node: Node): node is TypePredicateNode; + function isTypeReferenceNode(node: Node): node is TypeReferenceNode; + function isFunctionTypeNode(node: Node): node is FunctionTypeNode; + function isConstructorTypeNode(node: Node): node is ConstructorTypeNode; + function isTypeQueryNode(node: Node): node is TypeQueryNode; + function isTypeLiteralNode(node: Node): node is TypeLiteralNode; + function isArrayTypeNode(node: Node): node is ArrayTypeNode; + function isTupleTypeNode(node: Node): node is TupleTypeNode; + function isUnionTypeNode(node: Node): node is UnionTypeNode; + function isIntersectionTypeNode(node: Node): node is IntersectionTypeNode; + function isParenthesizedTypeNode(node: Node): node is ParenthesizedTypeNode; + function isThisTypeNode(node: Node): node is ThisTypeNode; + function isTypeOperatorNode(node: Node): node is TypeOperatorNode; + function isIndexedAccessTypeNode(node: Node): node is IndexedAccessTypeNode; + function isMappedTypeNode(node: Node): node is MappedTypeNode; + function isLiteralTypeNode(node: Node): node is LiteralTypeNode; + function isObjectBindingPattern(node: Node): node is ObjectBindingPattern; + function isArrayBindingPattern(node: Node): node is ArrayBindingPattern; + function isBindingElement(node: Node): node is BindingElement; + function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; + function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; + function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isCallExpression(node: Node): node is CallExpression; + function isNewExpression(node: Node): node is NewExpression; + function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; + function isTypeAssertion(node: Node): node is TypeAssertion; + function isParenthesizedExpression(node: Node): node is ParenthesizedExpression; + function skipPartiallyEmittedExpressions(node: Expression): Expression; + function skipPartiallyEmittedExpressions(node: Node): Node; + function isFunctionExpression(node: Node): node is FunctionExpression; + function isArrowFunction(node: Node): node is ArrowFunction; + function isDeleteExpression(node: Node): node is DeleteExpression; + function isTypeOfExpression(node: Node): node is TypeOfExpression; + function isVoidExpression(node: Node): node is VoidExpression; + function isAwaitExpression(node: Node): node is AwaitExpression; + function isPrefixUnaryExpression(node: Node): node is PrefixUnaryExpression; + function isPostfixUnaryExpression(node: Node): node is PostfixUnaryExpression; + function isBinaryExpression(node: Node): node is BinaryExpression; + function isConditionalExpression(node: Node): node is ConditionalExpression; + function isTemplateExpression(node: Node): node is TemplateExpression; + function isYieldExpression(node: Node): node is YieldExpression; + function isSpreadElement(node: Node): node is SpreadElement; + function isClassExpression(node: Node): node is ClassExpression; + function isOmittedExpression(node: Node): node is OmittedExpression; + function isExpressionWithTypeArguments(node: Node): node is ExpressionWithTypeArguments; + function isAsExpression(node: Node): node is AsExpression; + function isNonNullExpression(node: Node): node is NonNullExpression; + function isMetaProperty(node: Node): node is MetaProperty; + function isTemplateSpan(node: Node): node is TemplateSpan; + function isSemicolonClassElement(node: Node): node is SemicolonClassElement; + function isBlock(node: Node): node is Block; + function isVariableStatement(node: Node): node is VariableStatement; + function isEmptyStatement(node: Node): node is EmptyStatement; + function isExpressionStatement(node: Node): node is ExpressionStatement; + function isIfStatement(node: Node): node is IfStatement; + function isDoStatement(node: Node): node is DoStatement; + function isWhileStatement(node: Node): node is WhileStatement; + function isForStatement(node: Node): node is ForStatement; + function isForInStatement(node: Node): node is ForInStatement; + function isForOfStatement(node: Node): node is ForOfStatement; + function isContinueStatement(node: Node): node is ContinueStatement; + function isBreakStatement(node: Node): node is BreakStatement; + function isReturnStatement(node: Node): node is ReturnStatement; + function isWithStatement(node: Node): node is WithStatement; + function isSwitchStatement(node: Node): node is SwitchStatement; + function isLabeledStatement(node: Node): node is LabeledStatement; + function isThrowStatement(node: Node): node is ThrowStatement; + function isTryStatement(node: Node): node is TryStatement; + function isDebuggerStatement(node: Node): node is DebuggerStatement; + function isVariableDeclaration(node: Node): node is VariableDeclaration; + function isVariableDeclarationList(node: Node): node is VariableDeclarationList; + function isFunctionDeclaration(node: Node): node is FunctionDeclaration; + function isClassDeclaration(node: Node): node is ClassDeclaration; + function isInterfaceDeclaration(node: Node): node is InterfaceDeclaration; + function isTypeAliasDeclaration(node: Node): node is TypeAliasDeclaration; + function isEnumDeclaration(node: Node): node is EnumDeclaration; + function isModuleDeclaration(node: Node): node is ModuleDeclaration; + function isModuleBlock(node: Node): node is ModuleBlock; + function isCaseBlock(node: Node): node is CaseBlock; + function isNamespaceExportDeclaration(node: Node): node is NamespaceExportDeclaration; + function isImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration; + function isImportDeclaration(node: Node): node is ImportDeclaration; + function isImportClause(node: Node): node is ImportClause; + function isNamespaceImport(node: Node): node is NamespaceImport; + function isNamedImports(node: Node): node is NamedImports; + function isImportSpecifier(node: Node): node is ImportSpecifier; + function isExportAssignment(node: Node): node is ExportAssignment; + function isExportDeclaration(node: Node): node is ExportDeclaration; + function isNamedExports(node: Node): node is NamedExports; + function isExportSpecifier(node: Node): node is ExportSpecifier; + function isMissingDeclaration(node: Node): node is MissingDeclaration; + function isExternalModuleReference(node: Node): node is ExternalModuleReference; + function isJsxElement(node: Node): node is JsxElement; + function isJsxSelfClosingElement(node: Node): node is JsxSelfClosingElement; + function isJsxOpeningElement(node: Node): node is JsxOpeningElement; + function isJsxClosingElement(node: Node): node is JsxClosingElement; + function isJsxAttribute(node: Node): node is JsxAttribute; + function isJsxAttributes(node: Node): node is JsxAttributes; + function isJsxSpreadAttribute(node: Node): node is JsxSpreadAttribute; + function isJsxExpression(node: Node): node is JsxExpression; + function isCaseClause(node: Node): node is CaseClause; + function isDefaultClause(node: Node): node is DefaultClause; + function isHeritageClause(node: Node): node is HeritageClause; + function isCatchClause(node: Node): node is CatchClause; + function isPropertyAssignment(node: Node): node is PropertyAssignment; + function isShorthandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment; + function isSpreadAssignment(node: Node): node is SpreadAssignment; + function isEnumMember(node: Node): node is EnumMember; + function isSourceFile(node: Node): node is SourceFile; + function isBundle(node: Node): node is Bundle; + function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; + function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; + function isJSDocUnknownType(node: Node): node is JSDocUnknownType; + function isJSDocNullableType(node: Node): node is JSDocNullableType; + function isJSDocNonNullableType(node: Node): node is JSDocNonNullableType; + function isJSDocOptionalType(node: Node): node is JSDocOptionalType; + function isJSDocFunctionType(node: Node): node is JSDocFunctionType; + function isJSDocVariadicType(node: Node): node is JSDocVariadicType; + function isJSDoc(node: Node): node is JSDoc; + function isJSDocAugmentsTag(node: Node): node is JSDocAugmentsTag; + function isJSDocParameterTag(node: Node): node is JSDocParameterTag; + function isJSDocReturnTag(node: Node): node is JSDocReturnTag; + function isJSDocTypeTag(node: Node): node is JSDocTypeTag; + function isJSDocTemplateTag(node: Node): node is JSDocTemplateTag; + function isJSDocTypedefTag(node: Node): node is JSDocTypedefTag; + function isJSDocPropertyTag(node: Node): node is JSDocPropertyTag; + function isJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag; + function isJSDocTypeLiteral(node: Node): node is JSDocTypeLiteral; +} +declare namespace ts { + /** + * True if node is of some token syntax kind. + * For example, this is true for an IfKeyword but not for an IfStatement. + */ + function isToken(n: Node): boolean; + function isLiteralExpression(node: Node): node is LiteralExpression; + function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail; + function isStringTextContainingNode(node: Node): boolean; + function isModifier(node: Node): node is Modifier; + function isEntityName(node: Node): node is EntityName; + function isPropertyName(node: Node): node is PropertyName; + function isBindingName(node: Node): node is BindingName; + function isFunctionLike(node: Node): node is FunctionLike; + function isClassElement(node: Node): node is ClassElement; + function isClassLike(node: Node): node is ClassLikeDeclaration; + function isAccessor(node: Node): node is AccessorDeclaration; + function isTypeElement(node: Node): node is TypeElement; + function isObjectLiteralElementLike(node: Node): node is ObjectLiteralElementLike; + /** + * Node test that determines whether a node is a valid type node. + * This differs from the `isPartOfTypeNode` function which determines whether a node is *part* + * of a TypeNode. + */ + function isTypeNode(node: Node): node is TypeNode; + function isFunctionOrConstructorTypeNode(node: Node): node is FunctionTypeNode | ConstructorTypeNode; + function isPropertyAccessOrQualifiedName(node: Node): node is PropertyAccessExpression | QualifiedName; + function isCallLikeExpression(node: Node): node is CallLikeExpression; + function isCallOrNewExpression(node: Node): node is CallExpression | NewExpression; + function isTemplateLiteral(node: Node): node is TemplateLiteral; + function isAssertionExpression(node: Node): node is AssertionExpression; + function isIterationStatement(node: Node, lookInLabeledStatements: boolean): node is IterationStatement; + function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement; + function isCaseOrDefaultClause(node: Node): node is CaseOrDefaultClause; + /** True if node is of a kind that may contain comment text. */ + function isJSDocCommentContainingNode(node: Node): boolean; +} +declare namespace ts { + function createNode(kind: SyntaxKind, pos?: number, end?: number): Node; + /** + * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes + * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise, + * embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns + * a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned. + * + * @param node a given node to visit its children + * @param cbNode a callback to be invoked for all child nodes + * @param cbNodes a callback to be invoked for embedded array + * + * @remarks `forEachChild` must visit the children of a node in the order + * that they appear in the source code. The language service depends on this property to locate nodes by position. + */ + function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined; + function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile; + function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName; + /** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ + function parseJsonText(fileName: string, sourceText: string): JsonSourceFile; + function isExternalModule(file: SourceFile): boolean; + function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; +} +declare namespace ts { + function getEffectiveTypeRoots(options: CompilerOptions, host: { + directoryExists?: (directoryName: string) => boolean; + getCurrentDirectory?: () => string; + }): string[] | undefined; + /** + * @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown. + * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups + * is assumed to be the same as root directory of the project. + */ + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; + /** + * Given a set of options, returns the set of type directive names + * that should be included for this program automatically. + * This list could either come from the config file, + * or from enumerating the types root + initial secondary types lookup location. + * More type directives might appear in the program later as a result of loading actual source files; + * this list is only the set of defaults that are implicitly included. + */ + function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: ModuleResolutionHost): string[]; + /** + * Cached module resolutions per containing directory. + * This assumes that any module id will have the same resolution for sibling files located in the same folder. + */ + interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { + getOrCreateCacheForDirectory(directoryName: string): Map; + } + /** + * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory + * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. + */ + interface NonRelativeModuleNameResolutionCache { + getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache; + } + interface PerModuleNameCache { + get(directory: string): ResolvedModuleWithFailedLookupLocations; + set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void; + } + function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache; + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; + function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; + function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations; +} +declare namespace ts { + function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; + function createLiteral(value: string): StringLiteral; + function createLiteral(value: number): NumericLiteral; + function createLiteral(value: boolean): BooleanLiteral; + /** Create a string literal whose source text is read from a source node during emit. */ + function createLiteral(sourceNode: StringLiteral | NumericLiteral | Identifier): StringLiteral; + function createLiteral(value: string | number | boolean): PrimaryExpression; + function createNumericLiteral(value: string): NumericLiteral; + function createIdentifier(text: string): Identifier; + function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; + /** Create a unique temporary variable. */ + function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; + /** Create a unique temporary variable for use in a loop. */ + function createLoopVariable(): Identifier; + /** Create a unique name based on the supplied text. */ + function createUniqueName(text: string): Identifier; + /** Create a unique name generated for a node. */ + function getGeneratedNameForNode(node: Node): Identifier; + function createToken(token: TKind): Token; + function createSuper(): SuperExpression; + function createThis(): ThisExpression & Token; + function createNull(): NullLiteral & Token; + function createTrue(): BooleanLiteral & Token; + function createFalse(): BooleanLiteral & Token; + function createQualifiedName(left: EntityName, right: string | Identifier): QualifiedName; + function updateQualifiedName(node: QualifiedName, left: EntityName, right: Identifier): QualifiedName; + function createComputedPropertyName(expression: Expression): ComputedPropertyName; + function updateComputedPropertyName(node: ComputedPropertyName, expression: Expression): ComputedPropertyName; + function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultType?: TypeNode): TypeParameterDeclaration; + function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined): TypeParameterDeclaration; + function createParameter(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): ParameterDeclaration; + function updateParameter(node: ParameterDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): ParameterDeclaration; + function createDecorator(expression: Expression): Decorator; + function updateDecorator(node: Decorator, expression: Expression): Decorator; + function createPropertySignature(modifiers: ReadonlyArray | undefined, name: PropertyName | string, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature; + function updatePropertySignature(node: PropertySignature, modifiers: ReadonlyArray | undefined, name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature; + function createProperty(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertyDeclaration; + function updateProperty(node: PropertyDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertyDeclaration; + function createMethodSignature(typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): MethodSignature; + function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined): MethodSignature; + function createMethod(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): MethodDeclaration; + function updateMethod(node: MethodDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, questionToken: QuestionToken | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): MethodDeclaration; + function createConstructor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, body: Block | undefined): ConstructorDeclaration; + function updateConstructor(node: ConstructorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, body: Block | undefined): ConstructorDeclaration; + function createGetAccessor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): GetAccessorDeclaration; + function updateGetAccessor(node: GetAccessorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: PropertyName, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): GetAccessorDeclaration; + function createSetAccessor(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | PropertyName, parameters: ReadonlyArray, body: Block | undefined): SetAccessorDeclaration; + function updateSetAccessor(node: SetAccessorDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: PropertyName, parameters: ReadonlyArray, body: Block | undefined): SetAccessorDeclaration; + function createCallSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): CallSignatureDeclaration; + function updateCallSignature(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): CallSignatureDeclaration; + function createConstructSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructSignatureDeclaration; + function updateConstructSignature(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructSignatureDeclaration; + function createIndexSignature(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode): IndexSignatureDeclaration; + function updateIndexSignature(node: IndexSignatureDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode): IndexSignatureDeclaration; + function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode; + function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode): TypePredicateNode; + function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode): TypePredicateNode; + function createTypeReferenceNode(typeName: string | EntityName, typeArguments: ReadonlyArray | undefined): TypeReferenceNode; + function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined): TypeReferenceNode; + function createFunctionTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): FunctionTypeNode; + function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): FunctionTypeNode; + function createConstructorTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructorTypeNode; + function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructorTypeNode; + function createTypeQueryNode(exprName: EntityName): TypeQueryNode; + function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode; + function createTypeLiteralNode(members: ReadonlyArray): TypeLiteralNode; + function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray): TypeLiteralNode; + function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode; + function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode; + function createTupleTypeNode(elementTypes: ReadonlyArray): TupleTypeNode; + function updateTypleTypeNode(node: TupleTypeNode, elementTypes: ReadonlyArray): TupleTypeNode; + function createUnionTypeNode(types: TypeNode[]): UnionTypeNode; + function updateUnionTypeNode(node: UnionTypeNode, types: NodeArray): UnionTypeNode; + function createIntersectionTypeNode(types: TypeNode[]): IntersectionTypeNode; + function updateIntersectionTypeNode(node: IntersectionTypeNode, types: NodeArray): IntersectionTypeNode; + function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: ReadonlyArray): UnionOrIntersectionTypeNode; + function createParenthesizedType(type: TypeNode): ParenthesizedTypeNode; + function updateParenthesizedType(node: ParenthesizedTypeNode, type: TypeNode): ParenthesizedTypeNode; + function createThisTypeNode(): ThisTypeNode; + function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; + function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; + function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; + function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; + function createMappedTypeNode(readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode; + function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode; + function createLiteralTypeNode(literal: LiteralTypeNode["literal"]): LiteralTypeNode; + function updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]): LiteralTypeNode; + function createObjectBindingPattern(elements: ReadonlyArray): ObjectBindingPattern; + function updateObjectBindingPattern(node: ObjectBindingPattern, elements: ReadonlyArray): ObjectBindingPattern; + function createArrayBindingPattern(elements: ReadonlyArray): ArrayBindingPattern; + function updateArrayBindingPattern(node: ArrayBindingPattern, elements: ReadonlyArray): ArrayBindingPattern; + function createBindingElement(dotDotDotToken: DotDotDotToken | undefined, propertyName: string | PropertyName | undefined, name: string | BindingName, initializer?: Expression): BindingElement; + function updateBindingElement(node: BindingElement, dotDotDotToken: DotDotDotToken | undefined, propertyName: PropertyName | undefined, name: BindingName, initializer: Expression | undefined): BindingElement; + function createArrayLiteral(elements?: ReadonlyArray, multiLine?: boolean): ArrayLiteralExpression; + function updateArrayLiteral(node: ArrayLiteralExpression, elements: ReadonlyArray): ArrayLiteralExpression; + function createObjectLiteral(properties?: ReadonlyArray, multiLine?: boolean): ObjectLiteralExpression; + function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; + function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; + function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; + function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; + function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; + function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; + function createTypeAssertion(type: TypeNode, expression: Expression): TypeAssertion; + function updateTypeAssertion(node: TypeAssertion, type: TypeNode, expression: Expression): TypeAssertion; + function createParen(expression: Expression): ParenthesizedExpression; + function updateParen(node: ParenthesizedExpression, expression: Expression): ParenthesizedExpression; + function createFunctionExpression(modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block): FunctionExpression; + function updateFunctionExpression(node: FunctionExpression, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block): FunctionExpression; + function createArrowFunction(modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, equalsGreaterThanToken: EqualsGreaterThanToken | undefined, body: ConciseBody): ArrowFunction; + function updateArrowFunction(node: ArrowFunction, modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: ConciseBody): ArrowFunction; + function updateArrowFunction(node: ArrowFunction, modifiers: ReadonlyArray | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, equalsGreaterThanToken: Token, body: ConciseBody): ArrowFunction; + function createDelete(expression: Expression): DeleteExpression; + function updateDelete(node: DeleteExpression, expression: Expression): DeleteExpression; + function createTypeOf(expression: Expression): TypeOfExpression; + function updateTypeOf(node: TypeOfExpression, expression: Expression): TypeOfExpression; + function createVoid(expression: Expression): VoidExpression; + function updateVoid(node: VoidExpression, expression: Expression): VoidExpression; + function createAwait(expression: Expression): AwaitExpression; + function updateAwait(node: AwaitExpression, expression: Expression): AwaitExpression; + function createPrefix(operator: PrefixUnaryOperator, operand: Expression): PrefixUnaryExpression; + function updatePrefix(node: PrefixUnaryExpression, operand: Expression): PrefixUnaryExpression; + function createPostfix(operand: Expression, operator: PostfixUnaryOperator): PostfixUnaryExpression; + function updatePostfix(node: PostfixUnaryExpression, operand: Expression): PostfixUnaryExpression; + function createBinary(left: Expression, operator: BinaryOperator | BinaryOperatorToken, right: Expression): BinaryExpression; + function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken): BinaryExpression; + function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; + function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression): ConditionalExpression; + function updateConditional(node: ConditionalExpression, condition: Expression, whenTrue: Expression, whenFalse: Expression): ConditionalExpression; + function updateConditional(node: ConditionalExpression, condition: Expression, questionToken: Token, whenTrue: Expression, colonToken: Token, whenFalse: Expression): ConditionalExpression; + function createTemplateExpression(head: TemplateHead, templateSpans: ReadonlyArray): TemplateExpression; + function updateTemplateExpression(node: TemplateExpression, head: TemplateHead, templateSpans: ReadonlyArray): TemplateExpression; + function createTemplateHead(text: string): TemplateHead; + function createTemplateMiddle(text: string): TemplateMiddle; + function createTemplateTail(text: string): TemplateTail; + function createNoSubstitutionTemplateLiteral(text: string): NoSubstitutionTemplateLiteral; + function createYield(expression?: Expression): YieldExpression; + function createYield(asteriskToken: AsteriskToken, expression: Expression): YieldExpression; + function updateYield(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression): YieldExpression; + function createSpread(expression: Expression): SpreadElement; + function updateSpread(node: SpreadElement, expression: Expression): SpreadElement; + function createClassExpression(modifiers: ReadonlyArray | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassExpression; + function updateClassExpression(node: ClassExpression, modifiers: ReadonlyArray | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassExpression; + function createOmittedExpression(): OmittedExpression; + function createExpressionWithTypeArguments(typeArguments: ReadonlyArray, expression: Expression): ExpressionWithTypeArguments; + function updateExpressionWithTypeArguments(node: ExpressionWithTypeArguments, typeArguments: ReadonlyArray, expression: Expression): ExpressionWithTypeArguments; + function createAsExpression(expression: Expression, type: TypeNode): AsExpression; + function updateAsExpression(node: AsExpression, expression: Expression, type: TypeNode): AsExpression; + function createNonNullExpression(expression: Expression): NonNullExpression; + function updateNonNullExpression(node: NonNullExpression, expression: Expression): NonNullExpression; + function createMetaProperty(keywordToken: MetaProperty["keywordToken"], name: Identifier): MetaProperty; + function updateMetaProperty(node: MetaProperty, name: Identifier): MetaProperty; + function createTemplateSpan(expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; + function updateTemplateSpan(node: TemplateSpan, expression: Expression, literal: TemplateMiddle | TemplateTail): TemplateSpan; + function createSemicolonClassElement(): SemicolonClassElement; + function createBlock(statements: ReadonlyArray, multiLine?: boolean): Block; + function updateBlock(node: Block, statements: ReadonlyArray): Block; + function createVariableStatement(modifiers: ReadonlyArray | undefined, declarationList: VariableDeclarationList | ReadonlyArray): VariableStatement; + function updateVariableStatement(node: VariableStatement, modifiers: ReadonlyArray | undefined, declarationList: VariableDeclarationList): VariableStatement; + function createEmptyStatement(): EmptyStatement; + function createStatement(expression: Expression): ExpressionStatement; + function updateStatement(node: ExpressionStatement, expression: Expression): ExpressionStatement; + function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement): IfStatement; + function updateIf(node: IfStatement, expression: Expression, thenStatement: Statement, elseStatement: Statement | undefined): IfStatement; + function createDo(statement: Statement, expression: Expression): DoStatement; + function updateDo(node: DoStatement, statement: Statement, expression: Expression): DoStatement; + function createWhile(expression: Expression, statement: Statement): WhileStatement; + function updateWhile(node: WhileStatement, expression: Expression, statement: Statement): WhileStatement; + function createFor(initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement): ForStatement; + function updateFor(node: ForStatement, initializer: ForInitializer | undefined, condition: Expression | undefined, incrementor: Expression | undefined, statement: Statement): ForStatement; + function createForIn(initializer: ForInitializer, expression: Expression, statement: Statement): ForInStatement; + function updateForIn(node: ForInStatement, initializer: ForInitializer, expression: Expression, statement: Statement): ForInStatement; + function createForOf(awaitModifier: AwaitKeywordToken, initializer: ForInitializer, expression: Expression, statement: Statement): ForOfStatement; + function updateForOf(node: ForOfStatement, awaitModifier: AwaitKeywordToken, initializer: ForInitializer, expression: Expression, statement: Statement): ForOfStatement; + function createContinue(label?: string | Identifier): ContinueStatement; + function updateContinue(node: ContinueStatement, label: Identifier | undefined): ContinueStatement; + function createBreak(label?: string | Identifier): BreakStatement; + function updateBreak(node: BreakStatement, label: Identifier | undefined): BreakStatement; + function createReturn(expression?: Expression): ReturnStatement; + function updateReturn(node: ReturnStatement, expression: Expression | undefined): ReturnStatement; + function createWith(expression: Expression, statement: Statement): WithStatement; + function updateWith(node: WithStatement, expression: Expression, statement: Statement): WithStatement; + function createSwitch(expression: Expression, caseBlock: CaseBlock): SwitchStatement; + function updateSwitch(node: SwitchStatement, expression: Expression, caseBlock: CaseBlock): SwitchStatement; + function createLabel(label: string | Identifier, statement: Statement): LabeledStatement; + function updateLabel(node: LabeledStatement, label: Identifier, statement: Statement): LabeledStatement; + function createThrow(expression: Expression): ThrowStatement; + function updateThrow(node: ThrowStatement, expression: Expression): ThrowStatement; + function createTry(tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined): TryStatement; + function updateTry(node: TryStatement, tryBlock: Block, catchClause: CatchClause | undefined, finallyBlock: Block | undefined): TryStatement; + function createDebuggerStatement(): DebuggerStatement; + function createVariableDeclaration(name: string | BindingName, type?: TypeNode, initializer?: Expression): VariableDeclaration; + function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined): VariableDeclaration; + function createVariableDeclarationList(declarations: ReadonlyArray, flags?: NodeFlags): VariableDeclarationList; + function updateVariableDeclarationList(node: VariableDeclarationList, declarations: ReadonlyArray): VariableDeclarationList; + function createFunctionDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): FunctionDeclaration; + function updateFunctionDeclaration(node: FunctionDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, asteriskToken: AsteriskToken | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, body: Block | undefined): FunctionDeclaration; + function createClassDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassDeclaration; + function updateClassDeclaration(node: ClassDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier | undefined, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray, members: ReadonlyArray): ClassDeclaration; + function createInterfaceDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray | undefined, members: ReadonlyArray): InterfaceDeclaration; + function updateInterfaceDeclaration(node: InterfaceDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, typeParameters: ReadonlyArray | undefined, heritageClauses: ReadonlyArray | undefined, members: ReadonlyArray): InterfaceDeclaration; + function createTypeAliasDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, typeParameters: ReadonlyArray | undefined, type: TypeNode): TypeAliasDeclaration; + function updateTypeAliasDeclaration(node: TypeAliasDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, typeParameters: ReadonlyArray | undefined, type: TypeNode): TypeAliasDeclaration; + function createEnumDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, members: ReadonlyArray): EnumDeclaration; + function updateEnumDeclaration(node: EnumDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, members: ReadonlyArray): EnumDeclaration; + function createModuleDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: ModuleName, body: ModuleBody | undefined, flags?: NodeFlags): ModuleDeclaration; + function updateModuleDeclaration(node: ModuleDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: ModuleName, body: ModuleBody | undefined): ModuleDeclaration; + function createModuleBlock(statements: ReadonlyArray): ModuleBlock; + function updateModuleBlock(node: ModuleBlock, statements: ReadonlyArray): ModuleBlock; + function createCaseBlock(clauses: ReadonlyArray): CaseBlock; + function updateCaseBlock(node: CaseBlock, clauses: ReadonlyArray): CaseBlock; + function createNamespaceExportDeclaration(name: string | Identifier): NamespaceExportDeclaration; + function updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration; + function createImportEqualsDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; + function updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; + function createImportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier?: Expression): ImportDeclaration; + function updateImportDeclaration(node: ImportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression | undefined): ImportDeclaration; + function createImportClause(name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; + function updateImportClause(node: ImportClause, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; + function createNamespaceImport(name: Identifier): NamespaceImport; + function updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; + function createNamedImports(elements: ReadonlyArray): NamedImports; + function updateNamedImports(node: NamedImports, elements: ReadonlyArray): NamedImports; + function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + function createExportAssignment(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, isExportEquals: boolean, expression: Expression): ExportAssignment; + function updateExportAssignment(node: ExportAssignment, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, expression: Expression): ExportAssignment; + function createExportDeclaration(decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, exportClause: NamedExports | undefined, moduleSpecifier?: Expression): ExportDeclaration; + function updateExportDeclaration(node: ExportDeclaration, decorators: ReadonlyArray | undefined, modifiers: ReadonlyArray | undefined, exportClause: NamedExports | undefined, moduleSpecifier: Expression | undefined): ExportDeclaration; + function createNamedExports(elements: ReadonlyArray): NamedExports; + function updateNamedExports(node: NamedExports, elements: ReadonlyArray): NamedExports; + function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier; + function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier; + function createExternalModuleReference(expression: Expression): ExternalModuleReference; + function updateExternalModuleReference(node: ExternalModuleReference, expression: Expression): ExternalModuleReference; + function createJsxElement(openingElement: JsxOpeningElement, children: ReadonlyArray, closingElement: JsxClosingElement): JsxElement; + function updateJsxElement(node: JsxElement, openingElement: JsxOpeningElement, children: ReadonlyArray, closingElement: JsxClosingElement): JsxElement; + function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement; + function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxSelfClosingElement; + function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement; + function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes): JsxOpeningElement; + function createJsxClosingElement(tagName: JsxTagNameExpression): JsxClosingElement; + function updateJsxClosingElement(node: JsxClosingElement, tagName: JsxTagNameExpression): JsxClosingElement; + function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression): JsxAttribute; + function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression): JsxAttribute; + function createJsxAttributes(properties: ReadonlyArray): JsxAttributes; + function updateJsxAttributes(node: JsxAttributes, properties: ReadonlyArray): JsxAttributes; + function createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute; + function updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression): JsxSpreadAttribute; + function createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined): JsxExpression; + function updateJsxExpression(node: JsxExpression, expression: Expression | undefined): JsxExpression; + function createCaseClause(expression: Expression, statements: ReadonlyArray): CaseClause; + function updateCaseClause(node: CaseClause, expression: Expression, statements: ReadonlyArray): CaseClause; + function createDefaultClause(statements: ReadonlyArray): DefaultClause; + function updateDefaultClause(node: DefaultClause, statements: ReadonlyArray): DefaultClause; + function createHeritageClause(token: HeritageClause["token"], types: ReadonlyArray): HeritageClause; + function updateHeritageClause(node: HeritageClause, types: ReadonlyArray): HeritageClause; + function createCatchClause(variableDeclaration: string | VariableDeclaration | undefined, block: Block): CatchClause; + function updateCatchClause(node: CatchClause, variableDeclaration: VariableDeclaration | undefined, block: Block): CatchClause; + function createPropertyAssignment(name: string | PropertyName, initializer: Expression): PropertyAssignment; + function updatePropertyAssignment(node: PropertyAssignment, name: PropertyName, initializer: Expression): PropertyAssignment; + function createShorthandPropertyAssignment(name: string | Identifier, objectAssignmentInitializer?: Expression): ShorthandPropertyAssignment; + function updateShorthandPropertyAssignment(node: ShorthandPropertyAssignment, name: Identifier, objectAssignmentInitializer: Expression | undefined): ShorthandPropertyAssignment; + function createSpreadAssignment(expression: Expression): SpreadAssignment; + function updateSpreadAssignment(node: SpreadAssignment, expression: Expression): SpreadAssignment; + function createEnumMember(name: string | PropertyName, initializer?: Expression): EnumMember; + function updateEnumMember(node: EnumMember, name: PropertyName, initializer: Expression | undefined): EnumMember; + function updateSourceFileNode(node: SourceFile, statements: ReadonlyArray): SourceFile; + /** + * Creates a shallow, memberwise clone of a node for mutation. + */ + function getMutableClone(node: T): T; + /** + * Creates a synthetic statement to act as a placeholder for a not-emitted statement in + * order to preserve comments. + * + * @param original The original statement. + */ + function createNotEmittedStatement(original: Node): NotEmittedStatement; + /** + * Creates a synthetic expression to act as a placeholder for a not-emitted expression in + * order to preserve comments or sourcemap positions. + * + * @param expression The inner expression to emit. + * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. + */ + function createPartiallyEmittedExpression(expression: Expression, original?: Node): PartiallyEmittedExpression; + function updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression): PartiallyEmittedExpression; + function createCommaList(elements: ReadonlyArray): CommaListExpression; + function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; + function createBundle(sourceFiles: ReadonlyArray): Bundle; + function updateBundle(node: Bundle, sourceFiles: ReadonlyArray): Bundle; + function createImmediatelyInvokedFunctionExpression(statements: Statement[]): CallExpression; + function createImmediatelyInvokedFunctionExpression(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: Statement[]): CallExpression; + function createImmediatelyInvokedArrowFunction(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression; + function createComma(left: Expression, right: Expression): Expression; + function createLessThan(left: Expression, right: Expression): Expression; + function createAssignment(left: ObjectLiteralExpression | ArrayLiteralExpression, right: Expression): DestructuringAssignment; + function createAssignment(left: Expression, right: Expression): BinaryExpression; + function createStrictEquality(left: Expression, right: Expression): BinaryExpression; + function createStrictInequality(left: Expression, right: Expression): BinaryExpression; + function createAdd(left: Expression, right: Expression): BinaryExpression; + function createSubtract(left: Expression, right: Expression): BinaryExpression; + function createPostfixIncrement(operand: Expression): PostfixUnaryExpression; + function createLogicalAnd(left: Expression, right: Expression): BinaryExpression; + function createLogicalOr(left: Expression, right: Expression): BinaryExpression; + function createLogicalNot(operand: Expression): PrefixUnaryExpression; + function createVoidZero(): VoidExpression; + function createExportDefault(expression: Expression): ExportAssignment; + function createExternalModuleExport(exportName: Identifier): ExportDeclaration; + /** + * Clears any EmitNode entries from parse-tree nodes. + * @param sourceFile A source file. + */ + function disposeEmitNodes(sourceFile: SourceFile): void; + function setTextRange(range: T, location: TextRange | undefined): T; + /** + * Sets flags that control emit behavior of a node. + */ + function setEmitFlags(node: T, emitFlags: EmitFlags): T; + /** + * Gets a custom text range to use when emitting source maps. + */ + function getSourceMapRange(node: Node): SourceMapRange; + /** + * Sets a custom text range to use when emitting source maps. + */ + function setSourceMapRange(node: T, range: SourceMapRange | undefined): T; + /** + * Create an external source map source file reference + */ + function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource; + /** + * Gets the TextRange to use for source maps for a token of a node. + */ + function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined; + /** + * Sets the TextRange to use for source maps for a token of a node. + */ + function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined): T; + /** + * Gets a custom text range to use when emitting comments. + */ + function getCommentRange(node: Node): TextRange; + /** + * Sets a custom text range to use when emitting comments. + */ + function setCommentRange(node: T, range: TextRange): T; + function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined; + function setSyntheticLeadingComments(node: T, comments: SynthesizedComment[]): T; + function addSyntheticLeadingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; + function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined; + function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[]): T; + function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; + /** + * Gets the constant value to emit for an expression. + */ + function getConstantValue(node: PropertyAccessExpression | ElementAccessExpression): string | number; + /** + * Sets the constant value to emit for an expression. + */ + function setConstantValue(node: PropertyAccessExpression | ElementAccessExpression, value: string | number): PropertyAccessExpression | ElementAccessExpression; + /** + * Adds an EmitHelper to a node. + */ + function addEmitHelper(node: T, helper: EmitHelper): T; + /** + * Add EmitHelpers to a node. + */ + function addEmitHelpers(node: T, helpers: EmitHelper[] | undefined): T; + /** + * Removes an EmitHelper from a node. + */ + function removeEmitHelper(node: Node, helper: EmitHelper): boolean; + /** + * Gets the EmitHelpers of a node. + */ + function getEmitHelpers(node: Node): EmitHelper[] | undefined; + /** + * Moves matching emit helpers from a source node to a target node. + */ + function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean): void; + function setOriginalNode(node: T, original: Node | undefined): T; +} +declare namespace ts { + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + function visitNode(node: T, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T; + /** + * Visits a Node using the supplied visitor, possibly returning a new Node in its place. + * + * @param node The Node to visit. + * @param visitor The callback used to visit the Node. + * @param test A callback to execute to verify the Node is valid. + * @param lift An optional callback to execute to lift a NodeArray into a valid Node. + */ + function visitNode(node: T | undefined, visitor: Visitor, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T): T | undefined; + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + function visitNodes(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + /** + * Visits a NodeArray using the supplied visitor, possibly returning a new NodeArray in its place. + * + * @param nodes The NodeArray to visit. + * @param visitor The callback used to visit a Node. + * @param test A node test to execute for each node. + * @param start An optional value indicating the starting offset at which to start visiting. + * @param count An optional value indicating the maximum number of nodes to visit. + */ + function visitNodes(nodes: NodeArray | undefined, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray | undefined; + /** + * Starts a new lexical environment and visits a statement list, ending the lexical environment + * and merging hoisted declarations upon completion. + */ + function visitLexicalEnvironment(statements: NodeArray, visitor: Visitor, context: TransformationContext, start?: number, ensureUseStrict?: boolean): NodeArray; + /** + * Starts a new lexical environment and visits a parameter list, suspending the lexical + * environment upon completion. + */ + function visitParameterList(nodes: NodeArray, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes): NodeArray; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: FunctionBody, visitor: Visitor, context: TransformationContext): FunctionBody; + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: FunctionBody | undefined, visitor: Visitor, context: TransformationContext): FunctionBody | undefined; + /** + * Resumes a suspended lexical environment and visits a concise body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + function visitFunctionBody(node: ConciseBody, visitor: Visitor, context: TransformationContext): ConciseBody; + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + function visitEachChild(node: T, visitor: Visitor, context: TransformationContext): T; + /** + * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. + * + * @param node The Node whose children will be visited. + * @param visitor The callback used to visit each child. + * @param context A lexical environment context for the visitor. + */ + function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined; +} +declare namespace ts { + function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; +} +declare namespace ts { + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function resolveTripleslashReference(moduleName: string, containingFile: string): string; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + } + function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; + /** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @returns A 'Program' object. + */ + function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; +} +declare namespace ts { + function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + function readConfigFile(fileName: string, readFile: (path: string) => string | undefined): { + config?: any; + error?: Diagnostic; + }; + /** + * Parse the text of the tsconfig.json file + * @param fileName The path to the config file + * @param jsonText The text of the config file + */ + function parseConfigFileTextToJson(fileName: string, jsonText: string): { + config?: any; + error?: Diagnostic; + }; + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + function readJsonConfigFile(fileName: string, readFile: (path: string) => string | undefined): JsonSourceFile; + /** + * Convert the json syntax tree into the json value + */ + function convertToObject(sourceFile: JsonSourceFile, errors: Push): any; + /** + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ + function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray): ParsedCommandLine; + /** + * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ + function parseJsonSourceFileConfigFileContent(sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: ReadonlyArray): ParsedCommandLine; + function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: CompilerOptions; + errors: Diagnostic[]; + }; + function convertTypeAcquisitionFromJson(jsonOptions: any, basePath: string, configFileName?: string): { + options: TypeAcquisition; + errors: Diagnostic[]; + }; +} +declare namespace ts { + interface Node { + getSourceFile(): SourceFile; + getChildCount(sourceFile?: SourceFile): number; + getChildAt(index: number, sourceFile?: SourceFile): Node; + getChildren(sourceFile?: SourceFile): Node[]; + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; + getFullStart(): number; + getEnd(): number; + getWidth(sourceFile?: SourceFile): number; + getFullWidth(): number; + getLeadingTriviaWidth(sourceFile?: SourceFile): number; + getFullText(sourceFile?: SourceFile): string; + getText(sourceFile?: SourceFile): string; + getFirstToken(sourceFile?: SourceFile): Node; + getLastToken(sourceFile?: SourceFile): Node; + forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; + } + interface Identifier { + readonly text: string; + } + interface Symbol { + readonly name: string; + getFlags(): SymbolFlags; + getEscapedName(): __String; + getName(): string; + getDeclarations(): Declaration[] | undefined; + getDocumentationComment(): SymbolDisplayPart[]; + getJsDocTags(): JSDocTagInfo[]; + } + interface Type { + getFlags(): TypeFlags; + getSymbol(): Symbol | undefined; + getProperties(): Symbol[]; + getProperty(propertyName: string): Symbol | undefined; + getApparentProperties(): Symbol[]; + getCallSignatures(): Signature[]; + getConstructSignatures(): Signature[]; + getStringIndexType(): Type | undefined; + getNumberIndexType(): Type | undefined; + getBaseTypes(): BaseType[] | undefined; + getNonNullableType(): Type; + } + interface Signature { + getDeclaration(): SignatureDeclaration; + getTypeParameters(): TypeParameter[] | undefined; + getParameters(): Symbol[]; + getReturnType(): Type; + getDocumentationComment(): SymbolDisplayPart[]; + getJsDocTags(): JSDocTagInfo[]; + } + interface SourceFile { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + getLineEndOfPosition(pos: number): number; + getLineStarts(): ReadonlyArray; + getPositionOfLineAndCharacter(line: number, character: number): number; + update(newText: string, textChangeRange: TextChangeRange): SourceFile; + } + interface SourceFileLike { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + interface SourceMapSource { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + /** + * Represents an immutable snapshot of a script at a specified time.Once acquired, the + * snapshot is observably immutable. i.e. the same calls with the same parameters will return + * the same values. + */ + interface IScriptSnapshot { + /** Gets a portion of the script snapshot specified by [start, end). */ + getText(start: number, end: number): string; + /** Gets the length of this script snapshot. */ + getLength(): number; + /** + * Gets the TextChangeRange that describe how the text changed between this text and + * an older version. This information is used by the incremental parser to determine + * what sections of the script need to be re-parsed. 'undefined' can be returned if the + * change range cannot be determined. However, in that case, incremental parsing will + * not happen and the entire document will be re - parsed. + */ + getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; + /** Releases all resources held by this script snapshot */ + dispose?(): void; + } + namespace ScriptSnapshot { + function fromString(text: string): IScriptSnapshot; + } + interface PreProcessedFileInfo { + referencedFiles: FileReference[]; + typeReferenceDirectives: FileReference[]; + importedFiles: FileReference[]; + ambientExternalModules: string[]; + isLibFile: boolean; + } + interface HostCancellationToken { + isCancellationRequested(): boolean; + } + interface LanguageServiceHost { + getCompilationSettings(): CompilerOptions; + getNewLine?(): string; + getProjectVersion?(): string; + getScriptFileNames(): string[]; + getScriptKind?(fileName: string): ScriptKind; + getScriptVersion(fileName: string): string; + getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; + getLocalizedDiagnosticMessages?(): any; + getCancellationToken?(): HostCancellationToken; + getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + log?(s: string): void; + trace?(s: string): void; + error?(s: string): void; + useCaseSensitiveFileNames?(): boolean; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + readFile?(path: string, encoding?: string): string | undefined; + fileExists?(path: string): boolean; + getTypeRootsVersion?(): number; + resolveModuleNames?(moduleNames: string[], containingFile: string): ResolvedModule[]; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + directoryExists?(directoryName: string): boolean; + getDirectories?(directoryName: string): string[]; + /** + * Gets a set of custom transformers to use during emit. + */ + getCustomTransformers?(): CustomTransformers | undefined; + } + interface LanguageService { + cleanupSemanticCache(): void; + getSyntacticDiagnostics(fileName: string): Diagnostic[]; + getSemanticDiagnostics(fileName: string): Diagnostic[]; + getCompilerOptionsDiagnostics(): Diagnostic[]; + /** + * @deprecated Use getEncodedSyntacticClassifications instead. + */ + getSyntacticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + /** + * @deprecated Use getEncodedSemanticClassifications instead. + */ + getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[]; + getEncodedSyntacticClassifications(fileName: string, span: TextSpan): Classifications; + getEncodedSemanticClassifications(fileName: string, span: TextSpan): Classifications; + getCompletionsAtPosition(fileName: string, position: number): CompletionInfo; + getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails; + getCompletionEntrySymbol(fileName: string, position: number, entryName: string): Symbol; + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo; + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan; + getBreakpointStatementAtPosition(fileName: string, position: number): TextSpan; + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems; + getRenameInfo(fileName: string, position: number): RenameInfo; + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[]; + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; + getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[]; + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[]; + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[]; + findReferences(fileName: string, position: number): ReferencedSymbol[]; + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[]; + /** @deprecated */ + getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[]; + getNavigateToItems(searchValue: string, maxResultCount?: number, fileName?: string, excludeDtsFiles?: boolean): NavigateToItem[]; + getNavigationBarItems(fileName: string): NavigationBarItem[]; + getNavigationTree(fileName: string): NavigationTree; + getOutliningSpans(fileName: string): OutliningSpan[]; + getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; + getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; + getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; + getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; + getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; + getProgram(): Program; + dispose(): void; + } + interface Classifications { + spans: number[]; + endOfLineState: EndOfLineState; + } + interface ClassifiedSpan { + textSpan: TextSpan; + classificationType: ClassificationTypeNames; + } + /** + * Navigation bar interface designed for visual studio's dual-column layout. + * This does not form a proper tree. + * The navbar is returned as a list of top-level items, each of which has a list of child items. + * Child items always have an empty array for their `childItems`. + */ + interface NavigationBarItem { + text: string; + kind: ScriptElementKind; + kindModifiers: string; + spans: TextSpan[]; + childItems: NavigationBarItem[]; + indent: number; + bolded: boolean; + grayed: boolean; + } + /** + * Node in a tree of nested declarations in a file. + * The top node is always a script or module node. + */ + interface NavigationTree { + /** Name of the declaration, or a short description, e.g. "". */ + text: string; + kind: ScriptElementKind; + /** ScriptElementKindModifier separated by commas, e.g. "public,abstract" */ + kindModifiers: string; + /** + * Spans of the nodes that generated this declaration. + * There will be more than one if this is the result of merging. + */ + spans: TextSpan[]; + /** Present if non-empty */ + childItems?: NavigationTree[]; + } + interface TodoCommentDescriptor { + text: string; + priority: number; + } + interface TodoComment { + descriptor: TodoCommentDescriptor; + message: string; + position: number; + } + class TextChange { + span: TextSpan; + newText: string; + } + interface FileTextChanges { + fileName: string; + textChanges: TextChange[]; + } + interface CodeAction { + /** Description of the code action to display in the UI of the editor */ + description: string; + /** Text changes to apply to each file as part of the code action */ + changes: FileTextChanges[]; + } + /** + * A set of one or more available refactoring actions, grouped under a parent refactoring. + */ + interface ApplicableRefactorInfo { + /** + * The programmatic name of the refactoring + */ + name: string; + /** + * A description of this refactoring category to show to the user. + * If the refactoring gets inlined (see below), this text will not be visible. + */ + description: string; + /** + * Inlineable refactorings can have their actions hoisted out to the top level + * of a context menu. Non-inlineanable refactorings should always be shown inside + * their parent grouping. + * + * If not specified, this value is assumed to be 'true' + */ + inlineable?: boolean; + actions: RefactorActionInfo[]; + } + /** + * Represents a single refactoring action - for example, the "Extract Method..." refactor might + * offer several actions, each corresponding to a surround class or closure to extract into. + */ + interface RefactorActionInfo { + /** + * The programmatic name of the refactoring action + */ + name: string; + /** + * A description of this refactoring action to show to the user. + * If the parent refactoring is inlined away, this will be the only text shown, + * so this description should make sense by itself if the parent is inlineable=true + */ + description: string; + } + /** + * A set of edits to make in response to a refactor action, plus an optional + * location where renaming should be invoked from + */ + interface RefactorEditInfo { + edits: FileTextChanges[]; + renameFilename: string | undefined; + renameLocation: number | undefined; + } + interface TextInsertion { + newText: string; + /** The position in newText the caret should point to after the insertion. */ + caretOffset: number; + } + interface DocumentSpan { + textSpan: TextSpan; + fileName: string; + } + interface RenameLocation extends DocumentSpan { + } + interface ReferenceEntry extends DocumentSpan { + isWriteAccess: boolean; + isDefinition: boolean; + isInString?: true; + } + interface ImplementationLocation extends DocumentSpan { + kind: ScriptElementKind; + displayParts: SymbolDisplayPart[]; + } + interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; + } + enum HighlightSpanKind { + none = "none", + definition = "definition", + reference = "reference", + writtenReference = "writtenReference", + } + interface HighlightSpan { + fileName?: string; + isInString?: true; + textSpan: TextSpan; + kind: HighlightSpanKind; + } + interface NavigateToItem { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + matchKind: string; + isCaseSensitive: boolean; + fileName: string; + textSpan: TextSpan; + containerName: string; + containerKind: ScriptElementKind; + } + enum IndentStyle { + None = 0, + Block = 1, + Smart = 2, + } + interface EditorOptions { + BaseIndentSize?: number; + IndentSize: number; + TabSize: number; + NewLineCharacter: string; + ConvertTabsToSpaces: boolean; + IndentStyle: IndentStyle; + } + interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle; + } + interface FormatCodeOptions extends EditorOptions { + InsertSpaceAfterCommaDelimiter: boolean; + InsertSpaceAfterSemicolonInForStatements: boolean; + InsertSpaceBeforeAndAfterBinaryOperators: boolean; + InsertSpaceAfterConstructor?: boolean; + InsertSpaceAfterKeywordsInControlFlowStatements: boolean; + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean; + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean; + InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + InsertSpaceAfterTypeAssertion?: boolean; + InsertSpaceBeforeFunctionParenthesis?: boolean; + PlaceOpenBraceOnNewLineForFunctions: boolean; + PlaceOpenBraceOnNewLineForControlBlocks: boolean; + } + interface FormatCodeSettings extends EditorSettings { + insertSpaceAfterCommaDelimiter?: boolean; + insertSpaceAfterSemicolonInForStatements?: boolean; + insertSpaceBeforeAndAfterBinaryOperators?: boolean; + insertSpaceAfterConstructor?: boolean; + insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + insertSpaceAfterTypeAssertion?: boolean; + insertSpaceBeforeFunctionParenthesis?: boolean; + placeOpenBraceOnNewLineForFunctions?: boolean; + placeOpenBraceOnNewLineForControlBlocks?: boolean; + } + interface DefinitionInfo { + fileName: string; + textSpan: TextSpan; + kind: ScriptElementKind; + name: string; + containerKind: ScriptElementKind; + containerName: string; + } + interface ReferencedSymbolDefinitionInfo extends DefinitionInfo { + displayParts: SymbolDisplayPart[]; + } + interface ReferencedSymbol { + definition: ReferencedSymbolDefinitionInfo; + references: ReferenceEntry[]; + } + enum SymbolDisplayPartKind { + aliasName = 0, + className = 1, + enumName = 2, + fieldName = 3, + interfaceName = 4, + keyword = 5, + lineBreak = 6, + numericLiteral = 7, + stringLiteral = 8, + localName = 9, + methodName = 10, + moduleName = 11, + operator = 12, + parameterName = 13, + propertyName = 14, + punctuation = 15, + space = 16, + text = 17, + typeParameterName = 18, + enumMemberName = 19, + functionName = 20, + regularExpressionLiteral = 21, + } + interface SymbolDisplayPart { + text: string; + kind: string; + } + interface JSDocTagInfo { + name: string; + text?: string; + } + interface QuickInfo { + kind: ScriptElementKind; + kindModifiers: string; + textSpan: TextSpan; + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + interface RenameInfo { + canRename: boolean; + localizedErrorMessage: string; + displayName: string; + fullDisplayName: string; + kind: ScriptElementKind; + kindModifiers: string; + triggerSpan: TextSpan; + } + interface SignatureHelpParameter { + name: string; + documentation: SymbolDisplayPart[]; + displayParts: SymbolDisplayPart[]; + isOptional: boolean; + } + /** + * Represents a single signature to show in signature help. + * The id is used for subsequent calls into the language service to ask questions about the + * signature help item in the context of any documents that have been updated. i.e. after + * an edit has happened, while signature help is still active, the host can ask important + * questions like 'what parameter is the user currently contained within?'. + */ + interface SignatureHelpItem { + isVariadic: boolean; + prefixDisplayParts: SymbolDisplayPart[]; + suffixDisplayParts: SymbolDisplayPart[]; + separatorDisplayParts: SymbolDisplayPart[]; + parameters: SignatureHelpParameter[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + /** + * Represents a set of signature help items, and the preferred item that should be selected. + */ + interface SignatureHelpItems { + items: SignatureHelpItem[]; + applicableSpan: TextSpan; + selectedItemIndex: number; + argumentIndex: number; + argumentCount: number; + } + interface CompletionInfo { + isGlobalCompletion: boolean; + isMemberCompletion: boolean; + /** + * true when the current location also allows for a new identifier + */ + isNewIdentifierLocation: boolean; + entries: CompletionEntry[]; + } + interface CompletionEntry { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + sortText: string; + /** + * An optional span that indicates the text to be replaced by this completion item. It will be + * set if the required span differs from the one generated by the default replacement behavior and should + * be used in that case + */ + replacementSpan?: TextSpan; + } + interface CompletionEntryDetails { + name: string; + kind: ScriptElementKind; + kindModifiers: string; + displayParts: SymbolDisplayPart[]; + documentation: SymbolDisplayPart[]; + tags: JSDocTagInfo[]; + } + interface OutliningSpan { + /** The span of the document to actually collapse. */ + textSpan: TextSpan; + /** The span of the document to display when the user hovers over the collapsed span. */ + hintSpan: TextSpan; + /** The text to display in the editor for the collapsed region. */ + bannerText: string; + /** + * Whether or not this region should be automatically collapsed when + * the 'Collapse to Definitions' command is invoked. + */ + autoCollapse: boolean; + } + interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + enum OutputFileType { + JavaScript = 0, + SourceMap = 1, + Declaration = 2, + } + interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } + enum EndOfLineState { + None = 0, + InMultiLineCommentTrivia = 1, + InSingleQuoteStringLiteral = 2, + InDoubleQuoteStringLiteral = 3, + InTemplateHeadOrNoSubstitutionTemplate = 4, + InTemplateMiddleOrTail = 5, + InTemplateSubstitutionPosition = 6, + } + enum TokenClass { + Punctuation = 0, + Keyword = 1, + Operator = 2, + Comment = 3, + Whitespace = 4, + Identifier = 5, + NumberLiteral = 6, + StringLiteral = 7, + RegExpLiteral = 8, + } + interface ClassificationResult { + finalLexState: EndOfLineState; + entries: ClassificationInfo[]; + } + interface ClassificationInfo { + length: number; + classification: TokenClass; + } + interface Classifier { + /** + * Gives lexical classifications of tokens on a line without any syntactic context. + * For instance, a token consisting of the text 'string' can be either an identifier + * named 'string' or the keyword 'string', however, because this classifier is not aware, + * it relies on certain heuristics to give acceptable results. For classifications where + * speed trumps accuracy, this function is preferable; however, for true accuracy, the + * syntactic classifier is ideal. In fact, in certain editing scenarios, combining the + * lexical, syntactic, and semantic classifiers may issue the best user experience. + * + * @param text The text of a line to classify. + * @param lexState The state of the lexical classifier at the end of the previous line. + * @param syntacticClassifierAbsent Whether the client is *not* using a syntactic classifier. + * If there is no syntactic classifier (syntacticClassifierAbsent=true), + * certain heuristics may be used in its place; however, if there is a + * syntactic classifier (syntacticClassifierAbsent=false), certain + * classifications which may be incorrectly categorized will be given + * back as Identifiers in order to allow the syntactic classifier to + * subsume the classification. + * @deprecated Use getLexicalClassifications instead. + */ + getClassificationsForLine(text: string, lexState: EndOfLineState, syntacticClassifierAbsent: boolean): ClassificationResult; + getEncodedLexicalClassifications(text: string, endOfLineState: EndOfLineState, syntacticClassifierAbsent: boolean): Classifications; + } + enum ScriptElementKind { + unknown = "", + warning = "warning", + /** predefined type (void) or keyword (class) */ + keyword = "keyword", + /** top level script node */ + scriptElement = "script", + /** module foo {} */ + moduleElement = "module", + /** class X {} */ + classElement = "class", + /** var x = class X {} */ + localClassElement = "local class", + /** interface Y {} */ + interfaceElement = "interface", + /** type T = ... */ + typeElement = "type", + /** enum E */ + enumElement = "enum", + enumMemberElement = "enum member", + /** + * Inside module and script only + * const v = .. + */ + variableElement = "var", + /** Inside function */ + localVariableElement = "local var", + /** + * Inside module and script only + * function f() { } + */ + functionElement = "function", + /** Inside function */ + localFunctionElement = "local function", + /** class X { [public|private]* foo() {} } */ + memberFunctionElement = "method", + /** class X { [public|private]* [get|set] foo:number; } */ + memberGetAccessorElement = "getter", + memberSetAccessorElement = "setter", + /** + * class X { [public|private]* foo:number; } + * interface Y { foo:number; } + */ + memberVariableElement = "property", + /** class X { constructor() { } } */ + constructorImplementationElement = "constructor", + /** interface Y { ():number; } */ + callSignatureElement = "call", + /** interface Y { []:number; } */ + indexSignatureElement = "index", + /** interface Y { new():Y; } */ + constructSignatureElement = "construct", + /** function foo(*Y*: string) */ + parameterElement = "parameter", + typeParameterElement = "type parameter", + primitiveType = "primitive type", + label = "label", + alias = "alias", + constElement = "const", + letElement = "let", + directory = "directory", + externalModuleName = "external module name", + /** + * + */ + jsxAttribute = "JSX attribute", + } + enum ScriptElementKindModifier { + none = "", + publicMemberModifier = "public", + privateMemberModifier = "private", + protectedMemberModifier = "protected", + exportedModifier = "export", + ambientModifier = "declare", + staticModifier = "static", + abstractModifier = "abstract", + } + enum ClassificationTypeNames { + comment = "comment", + identifier = "identifier", + keyword = "keyword", + numericLiteral = "number", + operator = "operator", + stringLiteral = "string", + whiteSpace = "whitespace", + text = "text", + punctuation = "punctuation", + className = "class name", + enumName = "enum name", + interfaceName = "interface name", + moduleName = "module name", + typeParameterName = "type parameter name", + typeAliasName = "type alias name", + parameterName = "parameter name", + docCommentTagName = "doc comment tag name", + jsxOpenTagName = "jsx open tag name", + jsxCloseTagName = "jsx close tag name", + jsxSelfClosingTagName = "jsx self closing tag name", + jsxAttribute = "jsx attribute", + jsxText = "jsx text", + jsxAttributeStringLiteralValue = "jsx attribute string literal value", + } + enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + } +} +declare namespace ts { + function createClassifier(): Classifier; +} +declare namespace ts { + /** + * The document registry represents a store of SourceFile objects that can be shared between + * multiple LanguageService instances. A LanguageService instance holds on the SourceFile (AST) + * of files in the context. + * SourceFile objects account for most of the memory usage by the language service. Sharing + * the same DocumentRegistry instance between different instances of LanguageService allow + * for more efficient memory utilization since all projects will share at least the library + * file (lib.d.ts). + * + * A more advanced use of the document registry is to serialize sourceFile objects to disk + * and re-hydrate them when needed. + * + * To create a default DocumentRegistry, use createDocumentRegistry to create one, and pass it + * to all subsequent createLanguageService calls. + */ + interface DocumentRegistry { + /** + * Request a stored SourceFile with a given fileName and compilationSettings. + * The first call to acquire will call createLanguageServiceSourceFile to generate + * the SourceFile if was not found in the registry. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @parm scriptSnapshot Text of the file. Only used if the file was not found + * in the registry and a new one was created. + * @parm version Current version of the file. Only used if the file was not found + * in the registry and a new one was created. + */ + acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + acquireDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + /** + * Request an updated version of an already existing SourceFile with a given fileName + * and compilationSettings. The update will in-turn call updateLanguageServiceSourceFile + * to get an updated SourceFile. + * + * @param fileName The name of the file requested + * @param compilationSettings Some compilation settings like target affects the + * shape of a the resulting SourceFile. This allows the DocumentRegistry to store + * multiple copies of the same file for different compilation settings. + * @param scriptSnapshot Text of the file. + * @param version Current version of the file. + */ + updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + updateDocumentWithKey(fileName: string, path: Path, compilationSettings: CompilerOptions, key: DocumentRegistryBucketKey, scriptSnapshot: IScriptSnapshot, version: string, scriptKind?: ScriptKind): SourceFile; + getKeyForCompilationSettings(settings: CompilerOptions): DocumentRegistryBucketKey; + /** + * Informs the DocumentRegistry that a file is not needed any longer. + * + * Note: It is not allowed to call release on a SourceFile that was not acquired from + * this registry originally. + * + * @param fileName The name of the file to be released + * @param compilationSettings The compilation settings used to acquire the file + */ + releaseDocument(fileName: string, compilationSettings: CompilerOptions): void; + releaseDocumentWithKey(path: Path, key: DocumentRegistryBucketKey): void; + reportStats(): string; + } + type DocumentRegistryBucketKey = string & { + __bucketKey: any; + }; + function createDocumentRegistry(useCaseSensitiveFileNames?: boolean, currentDirectory?: string): DocumentRegistry; +} +declare namespace ts { + function preProcessFile(sourceText: string, readImportFiles?: boolean, detectJavaScriptImports?: boolean): PreProcessedFileInfo; +} +declare namespace ts { + interface TranspileOptions { + compilerOptions?: CompilerOptions; + fileName?: string; + reportDiagnostics?: boolean; + moduleName?: string; + renamedDependencies?: MapLike; + transformers?: CustomTransformers; + } + interface TranspileOutput { + outputText: string; + diagnostics?: Diagnostic[]; + sourceMapText?: string; + } + function transpileModule(input: string, transpileOptions: TranspileOptions): TranspileOutput; + function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string; +} +declare namespace ts { + /** The version of the language service API */ + const servicesVersion = "0.5"; + interface DisplayPartsSymbolWriter extends SymbolWriter { + displayParts(): SymbolDisplayPart[]; + } + function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; + function displayPartsToString(displayParts: SymbolDisplayPart[]): string; + function getDefaultCompilerOptions(): CompilerOptions; + function getSupportedCodeFixes(): string[]; + function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; + let disableIncrementalParsing: boolean; + function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; + function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; + /** + * Get the path of the default library files (lib.d.ts) as distributed with the typescript + * node package. + * The functionality is not supported if the ts module is consumed outside of a node module. + */ + function getDefaultLibFilePath(options: CompilerOptions): string; +} +declare namespace ts { + /** + * Transform one or more nodes using the supplied transformers. + * @param source A single `Node` or an array of `Node` objects. + * @param transformers An array of `TransformerFactory` callbacks used to process the transformation. + * @param compilerOptions Optional compiler options. + */ + function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions): TransformationResult; +} + +export = ts; \ No newline at end of file