Use native maps when they're available

This commit is contained in:
Andy Hanson
2016-09-26 11:33:25 -07:00
parent 3212e25a3a
commit aadcbcc083
66 changed files with 1678 additions and 1326 deletions

View File

@@ -347,25 +347,26 @@ namespace ts.server {
// Use slice to clone the array to avoid manipulating in place
const queue = fileInfo.referencedBy.slice(0);
const fileNameSet = createMap<ScriptInfo>();
fileNameSet[scriptInfo.fileName] = scriptInfo;
const fileNameSet = new StringMap<ScriptInfo>();
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[potentialFileInfo.scriptInfo.fileName]) {
if (!fileNameSet.get(potentialFileInfo.scriptInfo.fileName)) {
queue.push(potentialFileInfo);
}
}
}
fileNameSet[processingFileInfo.scriptInfo.fileName] = processingFileInfo.scriptInfo;
fileNameSet.set(processingFileInfo.scriptInfo.fileName, processingFileInfo.scriptInfo);
}
const result: string[] = [];
for (const fileName in fileNameSet) {
if (shouldEmitFile(fileNameSet[fileName])) {
fileNameSet.forEach((scriptInfo, fileName) => {
if (shouldEmitFile(scriptInfo)) {
result.push(fileName);
}
}
});
return result;
}
}

View File

@@ -21,7 +21,7 @@ namespace ts.server {
export class SessionClient implements LanguageService {
private sequence: number = 0;
private lineMaps: ts.Map<number[]> = ts.createMap<number[]>();
private lineMaps = new ts.StringMap<number[]>();
private messages: string[] = [];
private lastRenameEntry: RenameEntry;
@@ -37,10 +37,10 @@ namespace ts.server {
}
private getLineMap(fileName: string): number[] {
let lineMap = this.lineMaps[fileName];
let lineMap = this.lineMaps.get(fileName);
if (!lineMap) {
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
lineMap = setAndReturn(this.lineMaps, fileName, ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())));
}
return lineMap;
}
@@ -146,7 +146,7 @@ namespace ts.server {
changeFile(fileName: string, start: number, end: number, newText: string): void {
// clear the line map after an edit
this.lineMaps[fileName] = undefined;
this.lineMaps.set(fileName, undefined);
const lineOffset = this.positionToOneBasedLineOffset(fileName, start);
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);

View File

@@ -98,23 +98,24 @@ namespace ts.server {
/**
* a path to directory watcher map that detects added tsconfig files
**/
private readonly directoryWatchersForTsconfig: Map<FileWatcher> = createMap<FileWatcher>();
private readonly directoryWatchersForTsconfig = new StringMap<FileWatcher>();
/**
* count of how many projects are using the directory watcher.
* If the number becomes 0 for a watcher, then we should close it.
**/
private readonly directoryWatchersRefCount: Map<number> = createMap<number>();
private readonly directoryWatchersRefCount = new StringMap<number>();
constructor(private readonly projectService: ProjectService) {
}
stopWatchingDirectory(directory: string) {
// if the ref count for this directory watcher drops to 0, it's time to close it
this.directoryWatchersRefCount[directory]--;
if (this.directoryWatchersRefCount[directory] === 0) {
const refCount = this.directoryWatchersRefCount.get(directory) - 1;
this.directoryWatchersRefCount.set(directory, refCount);
if (refCount === 0) {
this.projectService.logger.info(`Close directory watcher for: ${directory}`);
this.directoryWatchersForTsconfig[directory].close();
delete this.directoryWatchersForTsconfig[directory];
this.directoryWatchersForTsconfig.get(directory).close();
this.directoryWatchersForTsconfig.delete(directory);
}
}
@@ -122,13 +123,13 @@ namespace ts.server {
let currentPath = getDirectoryPath(fileName);
let parentPath = getDirectoryPath(currentPath);
while (currentPath != parentPath) {
if (!this.directoryWatchersForTsconfig[currentPath]) {
if (!this.directoryWatchersForTsconfig.get(currentPath)) {
this.projectService.logger.info(`Add watcher for: ${currentPath}`);
this.directoryWatchersForTsconfig[currentPath] = this.projectService.host.watchDirectory(currentPath, callback);
this.directoryWatchersRefCount[currentPath] = 1;
this.directoryWatchersForTsconfig.set(currentPath, this.projectService.host.watchDirectory(currentPath, callback));
this.directoryWatchersRefCount.set(currentPath, 1);
}
else {
this.directoryWatchersRefCount[currentPath] += 1;
modifyValue(this.directoryWatchersRefCount, currentPath, count => count + 1);
}
project.directoriesWatchedForTsconfig.push(currentPath);
currentPath = parentPath;
@@ -150,7 +151,7 @@ namespace ts.server {
/**
* maps external project file name to list of config files that were the part of this project
*/
private readonly externalProjectToConfiguredProjectMap: Map<NormalizedPath[]> = createMap<NormalizedPath[]>();
private readonly externalProjectToConfiguredProjectMap = new StringMap<NormalizedPath[]>();
/**
* external projects (configuration and list of root files is not controlled by tsserver)
@@ -325,7 +326,7 @@ namespace ts.server {
}
else {
if (info && (!info.isOpen)) {
// file has been changed which might affect the set of referenced files in projects that include
// file has been changed which might affect the set of referenced files in projects that include
// this file and set of inferred projects
info.reloadFromFile();
this.updateProjectGraphs(info.containingProjects);
@@ -343,7 +344,7 @@ namespace ts.server {
if (!info.isOpen) {
this.filenameToScriptInfo.remove(info.path);
// capture list of projects since detachAllProjects will wipe out original list
// capture list of projects since detachAllProjects will wipe out original list
const containingProjects = info.containingProjects.slice();
info.detachAllProjects();
@@ -493,7 +494,7 @@ namespace ts.server {
const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info);
if (!this.useSingleInferredProject) {
// if useOneInferredProject is not set then try to fixup ownership of open files
// check 'defaultProject !== inferredProject' is necessary to handle cases
// check 'defaultProject !== inferredProject' is necessary to handle cases
// when creation inferred project for some file has added other open files into this project (i.e. as referenced files)
// we definitely don't want to delete the project that was just created
for (const f of this.openFiles) {
@@ -503,7 +504,7 @@ namespace ts.server {
}
const defaultProject = f.getDefaultProject();
if (isRootFileInInferredProject(info) && defaultProject !== inferredProject && inferredProject.containsScriptInfo(f)) {
// open file used to be root in inferred project,
// open file used to be root in inferred project,
// this inferred project is different from the one we've just created for current file
// and new inferred project references this open file.
// We should delete old inferred project and attach open file to the new one
@@ -715,7 +716,7 @@ namespace ts.server {
files: parsedCommandLine.fileNames,
compilerOptions: parsedCommandLine.options,
configHasFilesProperty: config["files"] !== undefined,
wildcardDirectories: createMap(parsedCommandLine.wildcardDirectories),
wildcardDirectories: parsedCommandLine.wildcardDirectories,
typingOptions: parsedCommandLine.typingOptions,
compileOnSave: parsedCommandLine.compileOnSave
};
@@ -771,7 +772,7 @@ namespace ts.server {
this.documentRegistry,
projectOptions.configHasFilesProperty,
projectOptions.compilerOptions,
projectOptions.wildcardDirectories,
mapOfMapLike(projectOptions.wildcardDirectories),
/*languageServiceEnabled*/ !sizeLimitExceeded,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave);
@@ -829,7 +830,7 @@ namespace ts.server {
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypingOptions: TypingOptions, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
const oldRootScriptInfos = project.getRootScriptInfos();
const newRootScriptInfos: ScriptInfo[] = [];
const newRootScriptInfoMap: NormalizedPathMap<ScriptInfo> = createNormalizedPathMap<ScriptInfo>();
const newRootScriptInfoMap: Map<NormalizedPath, ScriptInfo> = new StringMap<ScriptInfo>();
let projectErrors: Diagnostic[];
let rootFilesChanged = false;
@@ -857,7 +858,7 @@ namespace ts.server {
let toAdd: ScriptInfo[];
let toRemove: ScriptInfo[];
for (const oldFile of oldRootScriptInfos) {
if (!newRootScriptInfoMap.contains(oldFile.fileName)) {
if (!newRootScriptInfoMap.has(oldFile.fileName)) {
(toRemove || (toRemove = [])).push(oldFile);
}
}
@@ -874,7 +875,7 @@ namespace ts.server {
if (toAdd) {
for (const f of toAdd) {
if (f.isOpen && isRootFileInInferredProject(f)) {
// if file is already root in some inferred project
// if file is already root in some inferred project
// - remove the file from that project and delete the project if necessary
const inferredProject = f.containingProjects[0];
inferredProject.removeFile(f);
@@ -1023,7 +1024,7 @@ namespace ts.server {
this.logger.info(`Host information ${args.hostInfo}`);
}
if (args.formatOptions) {
mergeMaps(this.hostConfiguration.formatCodeOptions, args.formatOptions);
mergeMapLikes(this.hostConfiguration.formatCodeOptions, args.formatOptions);
this.logger.info("Format host information updated");
}
}
@@ -1145,7 +1146,7 @@ namespace ts.server {
for (const file of changedFiles) {
const scriptInfo = this.getScriptInfo(file.fileName);
Debug.assert(!!scriptInfo);
// apply changes in reverse order
// apply changes in reverse order
for (let i = file.changes.length - 1; i >= 0; i--) {
const change = file.changes[i];
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
@@ -1182,7 +1183,7 @@ namespace ts.server {
closeExternalProject(uncheckedFileName: string, suppressRefresh = false): void {
const fileName = toNormalizedPath(uncheckedFileName);
const configFiles = this.externalProjectToConfiguredProjectMap[fileName];
const configFiles = this.externalProjectToConfiguredProjectMap.get(fileName);
if (configFiles) {
let shouldRefreshInferredProjects = false;
for (const configFile of configFiles) {
@@ -1190,7 +1191,7 @@ namespace ts.server {
shouldRefreshInferredProjects = true;
}
}
delete this.externalProjectToConfiguredProjectMap[fileName];
this.externalProjectToConfiguredProjectMap.delete(fileName);
if (shouldRefreshInferredProjects && !suppressRefresh) {
this.refreshInferredProjects();
}
@@ -1237,7 +1238,7 @@ namespace ts.server {
// close existing project and later we'll open a set of configured projects for these files
this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true);
}
else if (this.externalProjectToConfiguredProjectMap[proj.projectFileName]) {
else if (this.externalProjectToConfiguredProjectMap.get(proj.projectFileName)) {
// this project used to include config files
if (!tsConfigFiles) {
// config files were removed from the project - close existing external project which in turn will close configured projects
@@ -1245,7 +1246,7 @@ namespace ts.server {
}
else {
// project previously had some config files - compare them with new set of files and close all configured projects that correspond to unused files
const oldConfigFiles = this.externalProjectToConfiguredProjectMap[proj.projectFileName];
const oldConfigFiles = this.externalProjectToConfiguredProjectMap.get(proj.projectFileName);
let iNew = 0;
let iOld = 0;
while (iNew < tsConfigFiles.length && iOld < oldConfigFiles.length) {
@@ -1273,7 +1274,7 @@ namespace ts.server {
}
if (tsConfigFiles) {
// store the list of tsconfig files that belong to the external project
this.externalProjectToConfiguredProjectMap[proj.projectFileName] = tsConfigFiles;
this.externalProjectToConfiguredProjectMap.set(proj.projectFileName, tsConfigFiles);
for (const tsconfigFile of tsConfigFiles) {
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
if (!project) {
@@ -1289,7 +1290,7 @@ namespace ts.server {
}
else {
// no config files - remove the item from the collection
delete this.externalProjectToConfiguredProjectMap[proj.projectFileName];
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
this.createAndAddExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typingOptions);
}
this.refreshInferredProjects();

View File

@@ -5,8 +5,8 @@
namespace ts.server {
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
private compilationSettings: ts.CompilerOptions;
private readonly resolvedModuleNames: ts.FileMap<Map<ResolvedModuleWithFailedLookupLocations>>;
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
private readonly resolvedModuleNames: ts.FileMap<Map<string, ResolvedModuleWithFailedLookupLocations>>;
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<string, ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
private readonly getCanonicalFileName: (fileName: string) => string;
private readonly resolveModuleName: typeof resolveModuleName;
@@ -14,8 +14,8 @@ namespace ts.server {
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
this.resolvedModuleNames = createFileMap<Map<string, ResolvedModuleWithFailedLookupLocations>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<string, ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
if (host.trace) {
this.trace = s => host.trace(s);
@@ -31,7 +31,7 @@ namespace ts.server {
}
}
// 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
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
const secondaryLookupFailedLookupLocations: string[] = [];
const globalCache = this.project.projectService.typingsInstaller.globalTypingsCacheLocation;
if (this.project.getTypingOptions().enableAutoDiscovery && globalCache) {
@@ -55,28 +55,28 @@ namespace ts.server {
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
names: string[],
containingFile: string,
cache: ts.FileMap<Map<T>>,
cache: ts.FileMap<Map<string, T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResult: (s: T) => R): R[] {
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = createMap<T>();
const newResolutions = new StringMap<T>();
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[name];
let resolution = newResolutions.get(name);
if (!resolution) {
const existingResolution = currentResolutionsInFile && currentResolutionsInFile[name];
const existingResolution = currentResolutionsInFile && currentResolutionsInFile.get(name);
if (moduleResolutionIsValid(existingResolution)) {
// ok, it is safe to use existing name resolution results
resolution = existingResolution;
}
else {
newResolutions[name] = resolution = loader(name, containingFile, compilerOptions, this);
newResolutions.set(name, resolution = loader(name, containingFile, compilerOptions, this));
}
}

View File

@@ -47,7 +47,7 @@ namespace ts.server {
/**
* Set of files that was returned from the last call to getChangesSinceVersion.
*/
private lastReportedFileNames: Map<string>;
private lastReportedFileNames: Map<string, string>;
/**
* Last version that was reported.
*/
@@ -437,16 +437,16 @@ namespace ts.server {
const added: string[] = [];
const removed: string[] = [];
for (const id in currentFiles) {
if (!hasProperty(lastReportedFileNames, id)) {
forEachKeyInMap(currentFiles, id => {
if (!lastReportedFileNames.has(id)) {
added.push(id);
}
}
for (const id in lastReportedFileNames) {
if (!hasProperty(currentFiles, id)) {
});
forEachKeyInMap(lastReportedFileNames, id => {
if (!currentFiles.has(id)) {
removed.push(id);
}
}
});
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectStructureVersion;
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
@@ -472,7 +472,7 @@ namespace ts.server {
// 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<boolean>();
const referencedFiles = new StringSet();
if (sourceFile.imports && sourceFile.imports.length > 0) {
const checker: TypeChecker = this.program.getTypeChecker();
for (const importName of sourceFile.imports) {
@@ -480,7 +480,7 @@ namespace ts.server {
if (symbol && symbol.declarations && symbol.declarations[0]) {
const declarationSourceFile = symbol.declarations[0].getSourceFile();
if (declarationSourceFile) {
referencedFiles[declarationSourceFile.path] = true;
referencedFiles.add(declarationSourceFile.path);
}
}
}
@@ -492,26 +492,24 @@ namespace ts.server {
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
for (const referencedFile of sourceFile.referencedFiles) {
const referencedPath = toPath(referencedFile.fileName, currentDirectory, getCanonicalFileName);
referencedFiles[referencedPath] = true;
referencedFiles.add(referencedPath);
}
}
// Handle type reference directives
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
for (const typeName in sourceFile.resolvedTypeReferenceDirectiveNames) {
const resolvedTypeReferenceDirective = sourceFile.resolvedTypeReferenceDirectiveNames[typeName];
sourceFile.resolvedTypeReferenceDirectiveNames.forEach(resolvedTypeReferenceDirective => {
if (!resolvedTypeReferenceDirective) {
continue;
return;
}
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
const typeFilePath = toPath(fileName, currentDirectory, getCanonicalFileName);
referencedFiles[typeFilePath] = true;
}
referencedFiles.add(typeFilePath);
});
}
const allFileNames = map(Object.keys(referencedFiles), key => <Path>key);
return filter(allFileNames, file => this.projectService.host.fileExists(file));
return filterSetToArray(referencedFiles, file => this.projectService.host.fileExists(file)) as Path[];
}
// remove a root file from project
@@ -582,7 +580,7 @@ namespace ts.server {
private typingOptions: TypingOptions;
private projectFileWatcher: FileWatcher;
private directoryWatcher: FileWatcher;
private directoriesWatchedForWildcards: Map<FileWatcher>;
private directoriesWatchedForWildcards: Map<string, FileWatcher>;
private typeRootsWatchers: FileWatcher[];
/** Used for configured projects which may have multiple open roots */
@@ -593,7 +591,7 @@ namespace ts.server {
documentRegistry: ts.DocumentRegistry,
hasExplicitListOfFiles: boolean,
compilerOptions: CompilerOptions,
private wildcardDirectories: Map<WatchDirectoryFlags>,
private wildcardDirectories: Map<string, WatchDirectoryFlags>,
languageServiceEnabled: boolean,
public compileOnSaveEnabled: boolean) {
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
@@ -648,18 +646,19 @@ namespace ts.server {
return;
}
const configDirectoryPath = getDirectoryPath(this.configFileName);
this.directoriesWatchedForWildcards = reduceProperties(this.wildcardDirectories, (watchers, flag, directory) => {
this.directoriesWatchedForWildcards = new StringMap<FileWatcher>();
this.wildcardDirectories.forEach((flag, directory) => {
if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) {
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
this.projectService.logger.info(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`);
watchers[directory] = this.projectService.host.watchDirectory(
this.directoriesWatchedForWildcards.set(directory, this.projectService.host.watchDirectory(
directory,
path => callback(this, path),
recursive
);
));
}
return watchers;
}, <Map<FileWatcher>>{});
});
}
stopWatchingDirectory() {
@@ -683,9 +682,7 @@ namespace ts.server {
this.typeRootsWatchers = undefined;
}
for (const id in this.directoriesWatchedForWildcards) {
this.directoriesWatchedForWildcards[id].close();
}
this.directoriesWatchedForWildcards.forEach(watcher => { watcher.close(); });
this.directoriesWatchedForWildcards = undefined;
this.stopWatchingDirectory();

View File

@@ -4,7 +4,7 @@ namespace ts.server {
export class ScriptInfo {
/**
* All projects that include this file
* All projects that include this file
*/
readonly containingProjects: Project[] = [];
private formatCodeSettings: ts.FormatCodeSettings;
@@ -96,7 +96,7 @@ namespace ts.server {
if (!this.formatCodeSettings) {
this.formatCodeSettings = getDefaultFormatCodeSettings(this.host);
}
mergeMaps(this.formatCodeSettings, formatSettings);
mergeMapLikes(this.formatCodeSettings, formatSettings);
}
}

View File

@@ -547,7 +547,7 @@ namespace ts.server {
const scriptInfo = this.projectService.getScriptInfo(args.file);
projects = scriptInfo.containingProjects;
}
// ts.filter handles case when 'projects' is undefined
// ts.filter handles case when 'projects' is undefined
projects = filter(projects, p => p.languageServiceEnabled);
if (!projects || !projects.length) {
return Errors.ThrowNoProject();
@@ -1279,7 +1279,7 @@ namespace ts.server {
return { response, responseRequired: true };
}
private handlers = createMap<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({
private handlers = mapOfMapLike<(request: protocol.Request) => { response?: any, responseRequired?: boolean }>({
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
this.projectService.openExternalProject(request.arguments);
// TODO: report errors
@@ -1525,14 +1525,14 @@ namespace ts.server {
});
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
if (command in this.handlers) {
if (this.handlers.has(command)) {
throw new Error(`Protocol handler already exists for command "${command}"`);
}
this.handlers[command] = handler;
this.handlers.set(command, handler);
}
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
const handler = this.handlers[request.command];
const handler = this.handlers.get(request.command);
if (handler) {
return handler(request);
}

View File

@@ -15,7 +15,7 @@
"utilities.ts",
"scriptVersionCache.ts",
"scriptInfo.ts",
"lshost.ts",
"lsHost.ts",
"typingsCache.ts",
"project.ts",
"editorServices.ts",

View File

@@ -29,21 +29,22 @@ namespace ts.server {
if ((arr1 || emptyArray).length === 0 && (arr2 || emptyArray).length === 0) {
return true;
}
const set: Map<boolean> = createMap<boolean>();
const set = new StringMap<boolean>();
let unique = 0;
for (const v of arr1) {
if (set[v] !== true) {
set[v] = true;
if (set.get(v) !== true) {
set.set(v, true);
unique++;
}
}
for (const v of arr2) {
if (!hasProperty(set, v)) {
const isSet = set.get(v);
if (isSet === undefined) {
return false;
}
if (set[v] === true) {
set[v] = false;
if (isSet === true) {
set.set(v, false);
unique--;
}
}
@@ -71,7 +72,7 @@ namespace ts.server {
}
export class TypingsCache {
private readonly perProjectCache: Map<TypingsCacheEntry> = createMap<TypingsCacheEntry>();
private readonly perProjectCache = new StringMap<TypingsCacheEntry>();
constructor(private readonly installer: ITypingsInstaller) {
}
@@ -83,17 +84,17 @@ namespace ts.server {
return <any>emptyArray;
}
const entry = this.perProjectCache[project.getProjectName()];
const entry = this.perProjectCache.get(project.getProjectName());
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
if (forceRefresh || !entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
// instead it acts as a placeholder to prevent issuing multiple requests
this.perProjectCache[project.getProjectName()] = {
this.perProjectCache.set(project.getProjectName(), {
compilerOptions: project.getCompilerOptions(),
typingOptions,
typings: result,
poisoned: true
};
});
// something has been changed, issue a request to update typings
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
}
@@ -109,16 +110,16 @@ namespace ts.server {
}
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, newTypings: string[]) {
this.perProjectCache[projectName] = {
this.perProjectCache.set(projectName, {
compilerOptions,
typingOptions,
typings: toTypingsArray(newTypings),
poisoned: false
};
});
}
onProjectClosed(project: Project) {
delete this.perProjectCache[project.getProjectName()];
this.perProjectCache.delete(project.getProjectName());
this.installer.onProjectClosed(project);
}
}

View File

@@ -35,7 +35,7 @@ namespace ts.server.typingsInstaller {
export const MaxPackageNameLength = 214;
/**
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
*/
export function validatePackageName(packageName: string): PackageNameValidationResult {
Debug.assert(!!packageName, "Package name is not specified");
@@ -75,10 +75,10 @@ namespace ts.server.typingsInstaller {
};
export abstract class TypingsInstaller {
private readonly packageNameToTypingLocation: Map<string> = createMap<string>();
private readonly missingTypingsSet: Map<true> = createMap<true>();
private readonly knownCachesSet: Map<true> = createMap<true>();
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
private readonly packageNameToTypingLocation = new StringMap<string>();
private readonly missingTypingsSet = new StringSet();
private readonly knownCachesSet = new StringSet();
private readonly projectWatchers = new StringMap<FileWatcher[]>();
readonly pendingRunRequests: PendingRequest[] = [];
private installRunCount = 1;
@@ -109,7 +109,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Closing file watchers for project '${projectName}'`);
}
const watchers = this.projectWatchers[projectName];
const watchers = this.projectWatchers.get(projectName);
if (!watchers) {
if (this.log.isEnabled()) {
this.log.writeLine(`No watchers are registered for project '${projectName}'`);
@@ -120,7 +120,7 @@ namespace ts.server.typingsInstaller {
w.close();
}
delete this.projectWatchers[projectName];
this.projectWatchers.delete(projectName);
if (this.log.isEnabled()) {
this.log.writeLine(`Closing file watchers for project '${projectName}' - done.`);
@@ -132,7 +132,7 @@ namespace ts.server.typingsInstaller {
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
}
// load existing typing information from the cache
// load existing typing information from the cache
if (req.cachePath) {
if (this.log.isEnabled()) {
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
@@ -174,7 +174,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
}
if (this.knownCachesSet[cacheLocation]) {
if (this.knownCachesSet.has(cacheLocation)) {
if (this.log.isEnabled()) {
this.log.writeLine(`Cache location was already processed...`);
}
@@ -200,7 +200,7 @@ namespace ts.server.typingsInstaller {
if (!typingFile) {
continue;
}
const existingTypingFile = this.packageNameToTypingLocation[packageName];
const existingTypingFile = this.packageNameToTypingLocation.get(packageName);
if (existingTypingFile === typingFile) {
continue;
}
@@ -212,14 +212,14 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
}
this.packageNameToTypingLocation[packageName] = typingFile;
this.packageNameToTypingLocation.set(packageName, typingFile);
}
}
}
if (this.log.isEnabled()) {
this.log.writeLine(`Finished processing cache location '${cacheLocation}'`);
}
this.knownCachesSet[cacheLocation] = true;
this.knownCachesSet.add(cacheLocation);
}
private filterTypings(typingsToInstall: string[]) {
@@ -228,7 +228,7 @@ namespace ts.server.typingsInstaller {
}
const result: string[] = [];
for (const typing of typingsToInstall) {
if (this.missingTypingsSet[typing]) {
if (this.missingTypingsSet.has(typing)) {
continue;
}
const validationResult = validatePackageName(typing);
@@ -237,7 +237,7 @@ namespace ts.server.typingsInstaller {
}
else {
// add typing name to missing set so we won't process it again
this.missingTypingsSet[typing] = true;
this.missingTypingsSet.add(typing);
if (this.log.isEnabled()) {
switch (validationResult) {
case PackageNameValidationResult.NameTooLong:
@@ -291,20 +291,20 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
}
const installedPackages: Map<true> = createMap<true>();
const installedPackages = new StringSet();
const installedTypingFiles: string[] = [];
for (const t of installedTypings) {
const packageName = getBaseFileName(t);
if (!packageName) {
continue;
}
installedPackages[packageName] = true;
installedPackages.add(packageName);
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost);
if (!typingFile) {
continue;
}
if (!this.packageNameToTypingLocation[packageName]) {
this.packageNameToTypingLocation[packageName] = typingFile;
if (!this.packageNameToTypingLocation.get(packageName)) {
this.packageNameToTypingLocation.set(packageName, typingFile);
}
installedTypingFiles.push(typingFile);
}
@@ -312,11 +312,11 @@ namespace ts.server.typingsInstaller {
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
}
for (const toInstall of typingsToInstall) {
if (!installedPackages[toInstall]) {
if (!installedPackages.has(toInstall)) {
if (this.log.isEnabled()) {
this.log.writeLine(`New missing typing package '${toInstall}'`);
}
this.missingTypingsSet[toInstall] = true;
this.missingTypingsSet.add(toInstall);
}
}
@@ -390,7 +390,7 @@ namespace ts.server.typingsInstaller {
});
watchers.push(w);
}
this.projectWatchers[projectName] = watchers;
this.projectWatchers.set(projectName, watchers);
}
private createSetTypings(request: DiscoverTypings, typings: string[]): SetTypings {

View File

@@ -90,7 +90,7 @@ namespace ts.server {
};
}
export function mergeMaps(target: MapLike<any>, source: MapLike <any>): void {
export function mergeMapLikes(target: MapLike<any>, source: MapLike <any>): void {
for (const key in source) {
if (hasProperty(source, key)) {
target[key] = source[key];
@@ -131,32 +131,6 @@ namespace ts.server {
return <NormalizedPath>fileName;
}
export interface NormalizedPathMap<T> {
get(path: NormalizedPath): T;
set(path: NormalizedPath, value: T): void;
contains(path: NormalizedPath): boolean;
remove(path: NormalizedPath): void;
}
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
/* tslint:disable:no-null-keyword */
const map: Map<T> = Object.create(null);
/* tslint:enable:no-null-keyword */
return {
get(path) {
return map[path];
},
set(path, value) {
map[path] = value;
},
contains(path) {
return hasProperty(map, path);
},
remove(path) {
delete map[path];
}
};
}
function throwLanguageServiceIsDisabledError() {
throw new Error("LanguageService is disabled");
}
@@ -223,7 +197,7 @@ namespace ts.server {
* these fields can be present in the project file
**/
files?: string[];
wildcardDirectories?: Map<WatchDirectoryFlags>;
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
compilerOptions?: CompilerOptions;
typingOptions?: TypingOptions;
compileOnSave?: boolean;
@@ -239,21 +213,22 @@ namespace ts.server {
}
export class ThrottledOperations {
private pendingTimeouts: Map<any> = createMap<any>();
private pendingTimeouts = new StringMap<any>();
constructor(private readonly host: ServerHost) {
}
public schedule(operationId: string, delay: number, cb: () => void) {
if (hasProperty(this.pendingTimeouts, operationId)) {
const pendingTimeout = this.pendingTimeouts.get(operationId);
if (pendingTimeout !== undefined) {
// another operation was already scheduled for this id - cancel it
this.host.clearTimeout(this.pendingTimeouts[operationId]);
this.host.clearTimeout(pendingTimeout);
}
// schedule new operation, pass arguments
this.pendingTimeouts[operationId] = this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb);
// schedule new operation, pass arguments
this.pendingTimeouts.set(operationId, this.host.setTimeout(ThrottledOperations.run, delay, this, operationId, cb));
}
private static run(self: ThrottledOperations, operationId: string, cb: () => void) {
delete self.pendingTimeouts[operationId];
self.pendingTimeouts.delete(operationId);
cb();
}
}