From 802e283aa7252b6ae4ddcd78dc2c88c754893361 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 18 Jul 2017 17:04:29 -0700 Subject: [PATCH 01/38] Refactoring of the builder --- src/compiler/utilities.ts | 8 +- src/server/builder.ts | 464 +++++++++++++++-------------------- src/server/editorServices.ts | 5 + src/server/project.ts | 23 +- src/server/session.ts | 4 +- src/server/utilities.ts | 14 +- 6 files changed, 225 insertions(+), 293 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 76d242e8b08d8..088733e21ef6b 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 8a10682b4cc22..285ab9ece6025 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -3,152 +3,105 @@ /// namespace ts.server { - export function shouldEmitFile(scriptInfo: ScriptInfo) { return !scriptInfo.hasMixedContent; } - /** - * 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); - } - + export interface Builder { /** - * 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. + * This is the callback when file infos in the builder are updated */ - 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); - } - + onProjectUpdateGraph(): void; + getFilesAffectedBy(scriptInfo: ScriptInfo): string[]; /** - * @return {boolean} indicates if the shape signature has changed since last update. + * @returns {boolean} whether the emit was conducted or not */ - 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 { - + interface EmitHandler { + addScriptInfo(scriptInfo: ScriptInfo): void; + removeScriptInfo(path: Path): void; + updateScriptInfo(scriptInfo: ScriptInfo): void; /** - * stores set of files from the project. - * NOTE: this field is created on demand and should not be accessed directly. - * Use 'getFileInfos' instead. + * Gets the files affected by the script info which has updated shape from the known one */ - 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; - } + getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[]; + } - public clear() { - // drop the existing list - it will be re-created as necessary - this.fileInfos_doNotAccessDirectly = undefined; - } + 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; + } - protected getFileInfo(path: Path): T { - return this.getFileInfos().get(path); + 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(); } - 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); + function ensureFileInfos() { + if (!emitHandler) { + createProjectGraph(); } - return fileInfo; + Debug.assert(projectVersionForDependencyGraph === project.getProjectVersion()); } - protected getFileInfoPaths(): Path[] { - return arrayFrom(this.getFileInfos().keys() as Iterator); + function onProjectUpdateGraph() { + if (emitHandler) { + createProjectGraph(); + } } - protected setFileInfo(path: Path, info: T) { - this.getFileInfos().set(path, info); - } + function getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { + ensureFileInfos(); - protected removeFileInfo(path: Path) { - this.getFileInfos().delete(path); - } + const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; + const path = scriptInfo.path; + if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(scriptInfo)) { + return singleFileResult; + } - protected forEachFileInfo(action: (fileInfo: T) => any) { - this.getFileInfos().forEach(action); + return emitHandler.getFilesAffectedByUpdatedShape(scriptInfo, singleFileResult); } - 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) { + 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 } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); + const { emitSkipped, outputFiles } = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ false); if (!emitSkipped) { - const projectRootPath = this.project.getProjectRootPath(); + 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); @@ -156,204 +109,175 @@ namespace ts.server { } return !emitSkipped; } - } - class NonModuleBuilder extends AbstractBuilder { + function clear() { + isModuleEmit = undefined; + emitHandler = undefined; + fileInfos = undefined; + projectVersionForDependencyGraph = undefined; + } - constructor(public readonly project: Project) { - super(project, BuilderFileInfo); + function getSourceFile(path: Path) { + return project.getSourceFile(path); } - protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) { - if (this.project.containsScriptInfo(scriptInfo)) { - this.getOrCreateFileInfo(scriptInfo.path); - } + function getScriptInfo(path: Path) { + return project.projectService.getScriptInfoForPath(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); - } - }); - } + function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) { + return sourceFile && (isExternalModule(sourceFile) || containsOnlyAmbientModules(sourceFile)); } /** - * 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. + * 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. */ - getFilesAffectedBy(scriptInfo: ScriptInfo): string[] { - const info = this.getOrCreateFileInfo(scriptInfo.path); - const singleFileResult = scriptInfo.hasMixedContent ? [] : [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; + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; } - 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); + return true; } - removeFileReferences() { - for (const reference of this.references) { - reference.removeReferencedBy(this); + /** + * @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; } - 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 prevSignature = fileInfos.get(path); + let latestSignature = prevSignature; + if (sourceFile.isDeclarationFile) { + latestSignature = computeHash(sourceFile.text); + fileInfos.set(path, latestSignature); + } + else { + const emitOutput = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ true); + if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { + latestSignature = computeHash(emitOutput.outputFiles[0].text); + fileInfos.set(path, latestSignature); + } } - const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path); - return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos); + return !prevSignature || latestSignature !== prevSignature; } - protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) { - this.ensureProjectDependencyGraphUpToDate(); + function computeHash(text: string) { + return project.projectService.host.createHash(text); } - onProjectUpdateGraph() { - // Update the graph only if we have computed graph earlier - if (this.hasFileInfos()) { - this.ensureProjectDependencyGraphUpToDate(); - } - } + function noop() { } - 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 getNonModuleEmitHandler(): EmitHandler { + return { + addScriptInfo: noop, + removeScriptInfo: noop, + updateScriptInfo: noop, + getFilesAffectedByUpdatedShape + }; + + 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; } - 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(); + return project.getAllEmittableFiles(); } } - private updateFileReferences(fileInfo: ModuleBuilderFileInfo) { - // Only need to update if the content of the file changed. - if (fileInfo.scriptVersionForReferences === fileInfo.scriptInfo.getLatestVersion()) { - return; + 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); } - 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(); + function addScriptInfo(info: ScriptInfo) { + setReferences(info.path, info.getLatestVersion(), undefined); + } - const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName]; - const fileInfo = this.getFileInfo(scriptInfo.path); - if (!fileInfo || !fileInfo.updateShapeSignature()) { - return singleFileResult; + function removeScriptInfo(path: Path) { + references.delete(path); + scriptVersionForReferences.delete(path); } - if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) { - return this.project.getAllEmittableFiles(); + 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)); + } } - const options = this.project.getCompilerOptions(); - if (options && (options.isolatedModules || options.out || options.outFile)) { - return singleFileResult; + function getReferencedByPaths(path: Path) { + return referencedBy.get(path) || []; } - // 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); + function getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[] { + const path = scriptInfo.path; + const sourceFile = getSourceFile(path); + if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) { + return project.getAllEmittableFiles(); + } + + 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. + + 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 003bb87f6a932..ad8e355a500f6 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 62c450f9f4f90..73d7f2d501729 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 0df2dd1e399aa..1e851d09615f1 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 6c884cebf5930..36b769182e510 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 02/38] 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 73d7f2d501729..483dfe841a700 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 03/38] 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 a8af11e780305..a578b7208ae50 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 efb574ac97107..5a12f3d0ce40e 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 54b772cc29c4a..2bec904437e1f 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 34727e4c41422..ac889ba9c045d 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 513c741ba0960..027d7ca202535 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 64e8eb0c80392..5999329a14cae 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 89cfb074bffed..8533ec95f3d66 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 088733e21ef6b..facb0bdc2ee6c 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 c6ec0f257b606..cd501b4395906 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 55a8f3ebb4d71..8f4dae7011602 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 7344c432b97ff..fd8d06427cfcb 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 eb2907e89dea6..5265a12355e52 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 2d50d2cb2af97..f3a5e930457e2 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 7f3428c839324..a856250307475 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 fa6ffc74e6557..ca86138803cb0 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 a7e0615dce866..4d23ca058058a 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 ad8e355a500f6..0b5cb8c119a82 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 2e031f3cad2f9..fb87c0065d55b 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 b7c85d2ce82d4..574ec70d36589 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 561c188c6cdf4..fc381ba8e5035 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 04/38] 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 5999329a14cae..8bb7427fa2feb 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 de5f5a756d866..a434ed8025dda 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 05/38] 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 | 685 ++++++++++++++++++++++-------- 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, 960 insertions(+), 508 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 5a12f3d0ce40e..e334889284449 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 2bec904437e1f..cbccdcbed02c6 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 8bb7427fa2feb..0c319b18053f5 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 db25afe45b62a..cf793919a4c43 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) { + 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); - return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } - if (configFileName) { - configFileWatcher = sys.watchFile(configFileName, configFileChanged); + } + + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { + if (compilerOptions.pretty) { + reportDiagnosticWorker = reportDiagnosticWithColorAndContext; } - 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); + + 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; + 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; } + + // 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)); } - performCompilation(); + 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 parseConfigFile(): ParsedCommandLine { - if (!cachedConfigFileText) { + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - cachedConfigFileText = sys.readFile(configFileName); + 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) { - const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + if (onError) { + onError(e.message); + } } } - if (!cachedConfigFileText) { - const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], /* compilerHost */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + + 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 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) { + 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()); + } - const result = parseJsonText(configFileName, cachedConfigFileText); - reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + return newSourceFile; + } - const cwd = sys.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); + return hostSourceFile.sourceFile; - 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); + 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; + } + } - 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); + function removeSourceFile(path: Path) { + const hostSourceFile = sourceFilesCache.get(path); + if (hostSourceFile !== undefined) { + if (!isString(hostSourceFile)) { + hostSourceFile.fileWatcher.close(); } + sourceFilesCache.delete(path); } - return configParseResult; } - // Invoked to perform initial compilation or re-compilation in watch mode - function performCompilation() { + function getSourceVersion(path: Path): string { + const hostSourceFile = sourceFilesCache.get(path); + return !hostSourceFile || isString(hostSourceFile) ? undefined : hostSourceFile.version.toString(); + } - 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; + 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); + } + } - hostFileExists = compilerHost.fileExists; - compilerHost.fileExists = cachedFileExists; + // 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 (compilerOptions.pretty) { - reportDiagnosticWorker = reportDiagnosticWithColorAndContext; + if (timerToUpdateProgram) { + sys.clearTimeout(timerToUpdateProgram); } + timerToUpdateProgram = sys.setTimeout(updateProgram, 250); + } - // reset the cache of existing files - cachedExistingFiles = createMap(); + function scheduleProgramReload() { + Debug.assert(!!configFileName); + needsReload = true; + scheduleProgramUpdate(); + } - const compileResult = compile(rootFileNames, compilerOptions, compilerHost); + function updateProgram() { + timerToUpdateProgram = undefined; + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - if (!isWatchSet(compilerOptions)) { - return sys.exit(compileResult.exitStatus); + if (needsReload) { + reloadConfigFile(); } + else { + synchronizeProgram(); + } + } - setCachedProgram(compileResult.program); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + function reloadConfigFile() { + writeLog(`Reloading config file: ${configFileName}`); + reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - const missingPaths = compileResult.program.getMissingFilePaths(); - missingPaths.forEach(path => { - const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => { - if (eventKind === FileWatcherEventKind.Created) { - fileWatcher.close(); - startTimerForRecompilation(); - } - }); - }); + 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 cachedFileExists(fileName: string): boolean { - let fileExists = cachedExistingFiles.get(fileName); - if (fileExists === undefined) { - cachedExistingFiles.set(fileName, fileExists = hostFileExists(fileName)); - } - return fileExists; + function watchSourceFileForChanges(path: Path) { + return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); } - 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 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()); + } } - } - // 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; - } + else { + // Deleted file created + if (isString(hostSourceFile)) { + sourceFilesCache.delete(path); } - }); + else { + // file changed - just update the version + hostSourceFile.version++; + } + } } - cachedProgram = program; + // Update the program + scheduleProgramUpdate(); } - // 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 watchMissingFilePath(missingFilePath: Path) { + return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); } - // If the configuration file changes, forget cached program and start the recompilation timer - function configFileChanged() { - setCachedProgram(undefined); - cachedConfigFileText = undefined; - startTimerForRecompilation(); + function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { + fileWatcher.close(); } - function watchedDirectoryChanged(fileName: string) { - if (fileName && !ts.isSupportedSourceFileName(fileName, compilerOptions)) { - return; + 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(); } + } - startTimerForHandlingDirectoryChanges(); + function watchConfigFileWildCardDirectories() { + const wildcards = createMapFromTemplate(configFileWildCardDirectories); + watchedWildCardDirectories = updateWatchingWildcardDirectories( + watchedWildCardDirectories, wildcards, + watchWildCardDirectory, stopWatchingWildCardDirectory + ); } - function startTimerForHandlingDirectoryChanges() { - if (!sys.setTimeout || !sys.clearTimeout) { + 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; } - if (timerHandleForDirectoryChanges) { - sys.clearTimeout(timerHandleForDirectoryChanges); - } - timerHandleForDirectoryChanges = sys.setTimeout(directoryChangeHandler, 250); - } + writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileName}`); - function directoryChangeHandler() { - const parsedCommandLine = parseConfigFile(); - const newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName); - const canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); + // 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; - // 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(); + // Schedule Update the program + scheduleProgramUpdate(); } } - // 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() { - if (!sys.setTimeout || !sys.clearTimeout) { - return; - } + function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { + writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + scheduleProgramReload(); + } - if (timerHandleForRecompilation) { - sys.clearTimeout(timerHandleForRecompilation); + function writeLog(s: string) { + const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + if (hasDiagnostics) { + host.write(s); } - timerHandleForRecompilation = sys.setTimeout(recompile, 250); } + } - function recompile() { - timerHandleForRecompilation = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); - performCompilation(); + 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) { + 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 8929b19bfe331..45f5a16a80c7d 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 facb0bdc2ee6c..08816382ecf64 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 0b5cb8c119a82..d617bdb2b9e05 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 fb1cd80b66063..692547e4915d9 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 483dfe841a700..d7c062178c442 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 36b769182e510..85bba441e51c7 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 fb87c0065d55b..e22d6041159c1 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 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): SourceFile { + 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 a5b035154beae..05d2ebf477cef 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 06/38] 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 285ab9ece6025..001bd708b2e10 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 d7c062178c442..851a26a5d4a9c 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 1e851d09615f1..31cb8c1e15d97 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 07/38] 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 001bd708b2e10..b830e517e5c16 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 0c319b18053f5..e2bfa647eb7d6 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 3709d65b7fd23..6c50bef7216cb 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 851a26a5d4a9c..d764e1701d8d9 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 e22d6041159c1..091364b5c50ee 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 6ff7402f3ad0c..34d79b311ca74 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 08/38] 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 b830e517e5c16..5661270e8f723 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 cf793919a4c43..e15a411d30fe7 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 d764e1701d8d9..942ff9f71a41f 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 09/38] 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 e15a411d30fe7..ec93b61bbdf53 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 0000000000000..3dba8961674bd --- /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 6c50bef7216cb..360819f2b3659 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 10/38] 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 8f4dae7011602..c0181199cbebc 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 c604b2246566a..22878f5a89e7d 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 66ca2fc3f4837..dfdd0a2fee853 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 5265a12355e52..12efa5717a35d 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 862ebee4b03ab..31de635609025 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 a856250307475..e7a64c59903a5 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 ca86138803cb0..b7c71f281916a 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 0000000000000..f37d685851c3c --- /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 11/38] 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 31243f77c4284..ba3e18f3e56c4 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 e2bfa647eb7d6..80bd089429a77 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 ec93b61bbdf53..3a672fca72728 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 3dba8961674bd..b92349061761e 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 dfdd0a2fee853..7c3ad0bc5a35c 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 0000000000000..630d9a71d201a --- /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 f37d685851c3c..45c0554bd9c7e 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 091364b5c50ee..8fe158a47984e 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 12/38] 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 5661270e8f723..c507b1570a85e 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 630d9a71d201a..1025e250d2780 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]); - }); - - it("emit with outFile or out setting projectUsesOutFile should be true if out is set", () => { - const outJS = "/a/out.js"; - const f1 = { + 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); + } + + 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"; + 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); - host.runQueuedTimeoutCallbacks(); - checkOutputContains(host, [emittedF1]); + + // 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 45c0554bd9c7e..95ef3266fb153 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 13/38] 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 | 354 ++++++++++++-------------- src/server/project.ts | 8 +- src/services/services.ts | 4 +- src/services/types.ts | 2 +- 8 files changed, 336 insertions(+), 320 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c507b1570a85e..489111f26db83 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 cbccdcbed02c6..2a41427f2fdbc 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 b92349061761e..13c1b27a5b094 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 }; - - function writeFiles(emitOutput: EmitOutputDetailed) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } + return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps }; - 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 08816382ecf64..0a38e6af58b06 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 1025e250d2780..7622bc2556be8 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" - // }` - // }; - - // 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] } - // ]); - //}); - }); + it("should always return the file itself if '--isolatedModules' is specified", () => { + const { + moduleFile1, verifyAffectedFiles + } = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1]); + }); + + 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) + }); + + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([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, verifyAffectedFiles, getFile + } = getInitialState({ + getAdditionalFileOrFolder: () => [file1Consumer1Consumer1] + }); + + const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path); + file1Consumer1.content += "export var T: number;"; + verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]); + + // Doesnt change the shape of file1Consumer1 + moduleFile1.content = `export var T: number;export function Foo() { };`; + verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]); + + // 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]); + }); + + 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 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); + + file1Emit.content += "export var t3 = 10;"; + verifyAffectedFiles([file1Emit, file2Emit], [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, + getFile, + verifyAffectedFiles + } = getInitialState({ + firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path], + getAdditionalFileOrFolder: () => [referenceFile1], + firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath] + }); + + const referenceFile1Emit = getFile(referenceFile1.path); + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + }); + + 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] + }); + + const referenceFile1Emit = getFile(referenceFile1.path); + referenceFile1Emit.content += "export var yy = Foo();"; + verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]); + + // 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 942ff9f71a41f..02e2fb20759a1 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 8fe158a47984e..40a446e0967a4 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 34d79b311ca74..e17df08378ad0 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 14/38] 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 13c1b27a5b094..36c9428cecd22 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 0a38e6af58b06..bea28ed27028a 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 7622bc2556be8..a55cfb133d78e 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 15/38] 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 | 733 ++------------------------ src/compiler/tsconfig.json | 1 + src/compiler/watchedProgram.ts | 673 +++++++++++++++++++++++ src/harness/unittests/tscWatchMode.ts | 101 ++-- 5 files changed, 795 insertions(+), 737 deletions(-) create mode 100644 src/compiler/watchedProgram.ts diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 80bd089429a77..8ce51ac5e8c04 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 36c9428cecd22..bd4a64d8899c2 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); - } - } - - interface HostFileInfo { - version: number; - sourceFile: SourceFile; - fileWatcher: FileWatcher; - } - - /* @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 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 writeLog(s: string) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - if (hasDiagnostics) { - host.write(s); - } - } + function performCompilation(rootFileNames: string[], compilerOptions: CompilerOptions) { + const compilerHost = createCompilerHost(compilerOptions); + enableStatistics(compilerOptions); - function computeHash(data: string) { - return system.createHash ? system.createHash(data) : data; - } - } + const program = createProgram(rootFileNames, compilerOptions, compilerHost); + const exitStatus = compileProgram(sys, program, () => program.emit(), reportDiagnostic); - interface CachedSystem extends System { - addOrDeleteFileOrFolder(fileOrFolder: string): void; - clearCache(): void; + reportStatistics(program); + return sys.exit(exitStatus); } - 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; + function enableStatistics(compilerOptions: CompilerOptions) { + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { + performance.enable(); } - - 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; + function reportStatistics(program: Program) { let statistics: Statistic[]; - if (hasDiagnostics) { - performance.enable(); + 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 360819f2b3659..fd92a6008cdce 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 0000000000000..22625282a8de3 --- /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 a55cfb133d78e..608fc30c6b936 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 16/38] 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 3a672fca72728..b89a41a2d2302 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 bd4a64d8899c2..0000000000000 --- 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 fd92a6008cdce..08d50e5eb82e6 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 608fc30c6b936..7c535e4068774 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 17/38] 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 0000000000000..f92464e5a3e63 --- /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 08d50e5eb82e6..1001f51f330b9 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 22625282a8de3..8bf5c54245672 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 692547e4915d9..274c3c3aef682 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>(); - - private filesWithChangedSetOfUnresolvedImports: Path[]; + /*@internal*/ + compilationSettings: CompilerOptions; - 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 02e2fb20759a1..ebad00e03be7b 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 18/38] 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 7c535e4068774..c9f6dcc053f1c 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 19/38] 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 c9f6dcc053f1c..8d6587fc2b389 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 20/38] 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 f92464e5a3e63..f2d20ab19a2ab 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 8bf5c54245672..6b16474e26897 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 ebad00e03be7b..c2fab978931d6 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 21/38] 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 f2d20ab19a2ab..6ca318f7183a5 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 6b16474e26897..c668d4a1510bd 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 b7c71f281916a..39776e379c9d2 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 d617bdb2b9e05..bf255c7defa17 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 c2fab978931d6..e600c3796e67d 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 22/38] 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 489111f26db83..c0af4f5b13d6e 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 2a41427f2fdbc..813d805adead8 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 027d7ca202535..3258bba665612 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 8ce51ac5e8c04..58931379d7c46 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 6ca318f7183a5..b6f3e7547f1c9 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 45f5a16a80c7d..c1ab964af304b 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 c668d4a1510bd..8628b6d049bcb 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 274c3c3aef682..f4cbd402604ca 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 e600c3796e67d..5ea54aca54eef 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 40a446e0967a4..14b5ddd85c4f8 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 e17df08378ad0..018e8941765ec 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 23/38] 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 | 168 +++++++++++++++----------------- 4 files changed, 157 insertions(+), 95 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c0af4f5b13d6e..7d4b1ce37a283 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 b6f3e7547f1c9..615340e487208 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 b89a41a2d2302..78586e5479a7e 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 8628b6d049bcb..d4301ea839dd0 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,23 +126,47 @@ 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[]; + 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) { + // 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()); - const result = builder.emitChangedFiles(program); - switch (result.length) { - case 0: + 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; - break; - case 1: - const emitOutput = result[0]; - ({ diagnostics, sourceMaps, emitSkipped } = emitOutput); - writeOutputFiles(emitOutput.outputFiles); - break; - default: + } + else { for (const emitOutput of result) { if (emitOutput.emitSkipped) { emitSkipped = true; @@ -168,70 +175,53 @@ namespace ts { 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); + 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))); + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - host.writeFile(fileName, data, writeByteOrderMark); + 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); + 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); + 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); } From 59d07dc4888da2e7ba9c1d9e8529b9141d552473 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Aug 2017 11:27:02 -0700 Subject: [PATCH 24/38] 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 19a18f7b9c1e4..849732f0afbf7 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 142d6351a461e..c642d99ff9d41 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 3f0fae850d691..6e26a287b8384 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 9984788403b71..2233ef1e94184 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 25/38] Updating according to feedback from PR --- src/compiler/builder.ts | 6 +++--- src/compiler/commandLineParser.ts | 10 ++++----- src/compiler/core.ts | 35 ++++++++++--------------------- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 19 ++++++++++------- src/compiler/watchedProgram.ts | 2 +- src/services/services.ts | 2 +- 7 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 849732f0afbf7..04d520cb2286c 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 3748dd9b9a440..ac8f1ee8f9796 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 c163de2446615..751177c4ef85b 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 c642d99ff9d41..2dd3e465268e4 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 193997a8a86a9..aafb97f4131f0 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 ca0292fa53321..5229e11ea9757 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 ba1c48b903786..b06f92344d3f1 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 26/38] 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 751177c4ef85b..1eab3582cb974 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 615340e487208..0379289cc8ee1 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 5229e11ea9757..e79049b983c06 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 8507b6f5cd27e..0b7ba9e744109 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 f4cbd402604ca..44b9893bc3d42 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 2233ef1e94184..552778e86c0ea 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 6d439e9238543..2dcbc52d87dde 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 27/38] 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 2dd3e465268e4..04af5c4d9726a 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 0379289cc8ee1..dc2e61cd57842 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 e4ea722df3dcf..9fe93f55cb3c4 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 6e26a287b8384..ebb6c7855885e 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 e79049b983c06..6423b9818969d 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 668b684a2501b..20323b3738085 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 552778e86c0ea..d50621c50bb25 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 28/38] 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 dc2e61cd57842..e50e32dfcef22 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 d50621c50bb25..fc6dca31ecdac 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 29/38] 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 e50e32dfcef22..4fefd87a834f7 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 9fe93f55cb3c4..87d0ad153c310 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 6423b9818969d..bd2473a699d6e 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 fc6dca31ecdac..c5ee3aa42bb39 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 30/38] 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 1eab3582cb974..e1193d7f71bf2 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 aafb97f4131f0..982d679381924 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 20323b3738085..ce8abc232e746 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 8deef58fd67877d6a5deccbf0f5530d7402c2856 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 12:15:03 -0700 Subject: [PATCH 31/38] 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 04af5c4d9726a..c3b83e1ba7c61 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 2243dfc2b1d68..a2141d640350d 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 7173da2134d82bbd17c1aa2d97bd083fe63f9d88 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 18 Aug 2017 15:38:47 -0700 Subject: [PATCH 32/38] 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 cccbcfa32c0c0..86fe129a1c7b5 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 33/38] 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 | 250 +++++++++++++++--- src/harness/virtualFileSystemWithWatch.ts | 9 +- src/server/editorServices.ts | 15 +- src/server/project.ts | 11 +- 9 files changed, 260 insertions(+), 84 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c93c9639ab268..00e8065a9f903 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 4fefd87a834f7..e331133fe7449 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 bd2473a699d6e..084c707cea8fa 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 079eabebc1e63..b7b382e5a1d0e 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 e8cba10f0880c..f5ed08b9b872c 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 86fe129a1c7b5..37cbd20521d1f 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); + + 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(); - 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 { 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 b77618df36061..44beb83922f3d 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 fb4419df79d93..6cbc5263249a0 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 d8344bd6a2ca5..b35298b293867 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 34/38] 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 0fe37dd8ba0d5..0ccff7c4d2521 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 35/38] 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 e331133fe7449..6d7c1f6c0e4f6 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 6cbc5263249a0..884ee518a30b9 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 36/38] 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 37cbd20521d1f..58c2b3a614d7f 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 37/38] Add api in builder to get changed files and use it to send project changed event --- src/compiler/builder.ts | 224 ++++++++++-------- .../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, 226 insertions(+), 144 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 04d520cb2286c..d96c3a547c0c8 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); - - 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); - } - } + 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)); + } + }); + } + + 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); + + 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 58c2b3a614d7f..3833089743b2f 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 884ee518a30b9..385ade0b81644 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 b35298b293867..cce0fc5b5103a 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 f50c873112671..3cc8555032fb1 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 d4fc22425a065..4b42aaba96a2d 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 38/38] 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 d96c3a547c0c8..4fb90ca7143f5 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 3833089743b2f..3c394da4e3167 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(); + } + } + }); + }); }