[in progress] project system work - versions

This commit is contained in:
Vladimir Matveev 2016-06-22 16:51:09 -07:00
parent c9b82eddda
commit c8d37dc87e
7 changed files with 354 additions and 317 deletions

View File

@ -28,7 +28,7 @@ namespace ts.server {
hostInfo: string;
}
function findVersionedProjectByFileName<T extends VersionedProject>(projectName: string, projects: T[]): T {
function findProjectByName<T extends Project>(projectName: string, projects: T[]): T {
for (const proj of projects) {
if (proj.getProjectName() === projectName) {
return proj;
@ -42,7 +42,54 @@ namespace ts.server {
project?: Project;
}
class DirectoryWatchers {
/**
* a path to directory watcher map that detects added tsconfig files
**/
private directoryWatchersForTsconfig: ts.Map<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 directoryWatchersRefCount: ts.Map<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) {
this.projectService.log("Close directory watcher for: " + directory);
this.directoryWatchersForTsconfig[directory].close();
delete this.directoryWatchersForTsconfig[directory];
}
}
startWatchingContainingDirectoriesForFile(fileName: string, project: InferredProject, callback: (fileName: string) => void) {
let currentPath = ts.getDirectoryPath(fileName);
let parentPath = ts.getDirectoryPath(currentPath);
while (currentPath != parentPath) {
if (!this.directoryWatchersForTsconfig[currentPath]) {
this.projectService.log("Add watcher for: " + currentPath);
this.directoryWatchersForTsconfig[currentPath] = this.projectService.host.watchDirectory(currentPath, callback);
this.directoryWatchersRefCount[currentPath] = 1;
}
else {
this.directoryWatchersRefCount[currentPath] += 1;
}
project.directoriesWatchedForTsconfig.push(currentPath);
currentPath = parentPath;
parentPath = ts.getDirectoryPath(parentPath);
}
}
}
export class ProjectService {
/**
* Container of all known scripts
*/
private filenameToScriptInfo = createNormalizedPathMap<ScriptInfo>();
/**
* maps external project file name to list of config files that were the part of this project
@ -64,10 +111,9 @@ namespace ts.server {
/**
* open, non-configured root files
**/
openFileRoots: ScriptInfo[] = [];
/**
* open files referenced by a project
* open files referenced by some project
**/
openFilesReferenced: ScriptInfo[] = [];
/**
@ -75,25 +121,19 @@ namespace ts.server {
**/
openFileRootsConfigured: ScriptInfo[] = [];
/**
* a path to directory watcher map that detects added tsconfig files
**/
private directoryWatchersForTsconfig: ts.Map<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 directoryWatchersRefCount: ts.Map<number> = {};
private directoryWatchers: DirectoryWatchers;
private hostConfiguration: HostConfiguration;
private timerForDetectingProjectFileListChanges: Map<any> = {};
private documentRegistry: ts.DocumentRegistry;
constructor(public host: ServerHost,
public psLogger: Logger,
public cancellationToken: HostCancellationToken,
public eventHandler?: ProjectServiceEventHandler) {
constructor(public readonly host: ServerHost,
public readonly psLogger: Logger,
public readonly cancellationToken: HostCancellationToken,
private readonly eventHandler?: ProjectServiceEventHandler) {
this.directoryWatchers = new DirectoryWatchers(this);
// ts.disableIncrementalParsing = true;
this.setDefaultHostConfiguration();
this.documentRegistry = ts.createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
@ -107,13 +147,7 @@ namespace ts.server {
}
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) {
this.log("Close directory watcher for: " + directory);
this.directoryWatchersForTsconfig[directory].close();
delete this.directoryWatchersForTsconfig[directory];
}
this.directoryWatchers.stopWatchingDirectory(directory);
}
findProject(projectName: string): Project {
@ -302,10 +336,18 @@ namespace ts.server {
return undefined;
}
private findContainingExternalProject(fileName: NormalizedPath): ExternalProject {
for (const proj of this.externalProjects) {
if (proj.containsFile(fileName)) {
return proj;
}
}
return undefined;
}
private addOpenFile(info: ScriptInfo) {
const externalProject = this.findContainingExternalProject(info.fileName);
if (externalProject) {
// info.defaultProject = externalProject;
return;
}
const configuredProject = this.findContainingConfiguredProject(info);
@ -403,15 +445,6 @@ namespace ts.server {
info.isOpen = false;
}
private findContainingExternalProject(fileName: NormalizedPath): ExternalProject {
for (const proj of this.externalProjects) {
if (proj.containsFile(fileName)) {
return proj;
}
}
return undefined;
}
/**
* This function tries to search for a tsconfig.json for the given file. If we found it,
* we first detect if there is already a configured project created for it: if so, we re-read
@ -511,11 +544,11 @@ namespace ts.server {
}
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {
return findVersionedProjectByFileName(configFileName, this.configuredProjects);
return findProjectByName(configFileName, this.configuredProjects);
}
private findExternalProjectByProjectName(projectFileName: string) {
return findVersionedProjectByFileName(projectFileName, this.externalProjects);
return findProjectByName(projectFileName, this.externalProjects);
}
private configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, errors?: Diagnostic[] } {
@ -580,7 +613,7 @@ namespace ts.server {
return { project, errors };
}
private createAndAddConfiguredProject(configFileName: string, projectOptions: ProjectOptions, clientFileName?: string) {
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, clientFileName?: string) {
const sizeLimitExceeded = this.exceedTotalNonTsFileSizeLimit(projectOptions.compilerOptions, projectOptions.files);
const project = new ConfiguredProject(
configFileName,
@ -611,7 +644,7 @@ namespace ts.server {
let errors: Diagnostic[];
for (const rootFilename of files) {
if (this.host.fileExists(rootFilename)) {
const info = this.getOrCreateScriptInfo(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName == rootFilename);
const info = this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(rootFilename), /*openedByClient*/ clientFileName == rootFilename);
project.addRoot(info);
}
else {
@ -622,7 +655,7 @@ namespace ts.server {
return errors;
}
private openConfigFile(configFileName: string, clientFileName?: string): { success: boolean, project?: ConfiguredProject, errors?: Diagnostic[] } {
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string): { success: boolean, project?: ConfiguredProject, errors?: Diagnostic[] } {
const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFileName);
if (!succeeded) {
return { success: false, errors };
@ -633,7 +666,7 @@ namespace ts.server {
}
}
private updateVersionedProjectWorker(project: VersionedProject, newRootFiles: string[], newOptions: CompilerOptions) {
private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newRootFiles: string[], newOptions: CompilerOptions) {
const oldRootFiles = project.getRootFiles();
const newFileNames = asNormalizedPathArray(filter(newRootFiles, f => this.host.fileExists(f)));
const fileNamesToRemove = oldRootFiles.filter(f => !contains(newFileNames, f));
@ -649,7 +682,7 @@ namespace ts.server {
for (const fileName of fileNamesToAdd) {
let info = this.getScriptInfo(fileName);
if (!info) {
info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
}
else {
// if the root file was opened by client, it would belong to either
@ -710,7 +743,7 @@ namespace ts.server {
project.enableLanguageService();
}
this.watchConfigDirectoryForProject(project, projectOptions);
this.updateVersionedProjectWorker(project, projectOptions.files, projectOptions.compilerOptions);
this.updateNonInferredProject(project, projectOptions.files, projectOptions.compilerOptions);
}
}
}
@ -720,21 +753,10 @@ namespace ts.server {
const project = new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true);
project.addRoot(root);
let currentPath = ts.getDirectoryPath(root.fileName);
let parentPath = ts.getDirectoryPath(currentPath);
while (currentPath != parentPath) {
if (!this.directoryWatchersForTsconfig[currentPath]) {
this.log("Add watcher for: " + currentPath);
this.directoryWatchersForTsconfig[currentPath] = this.host.watchDirectory(currentPath, fileName => this.onConfigChangeForInferredProject(fileName));
this.directoryWatchersRefCount[currentPath] = 1;
}
else {
this.directoryWatchersRefCount[currentPath] += 1;
}
project.directoriesWatchedForTsconfig.push(currentPath);
currentPath = parentPath;
parentPath = ts.getDirectoryPath(parentPath);
}
this.directoryWatchers.startWatchingContainingDirectoriesForFile(
root.fileName,
project,
fileName => this.onConfigChangeForInferredProject(fileName));
project.updateGraph();
this.inferredProjects.push(project);
@ -745,7 +767,11 @@ namespace ts.server {
* @param filename is absolute pathname
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
getOrCreateScriptInfo(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
getOrCreateScriptInfo(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
return this.getOrCreateScriptInfoForNormalizedPath(toNormalizedPath(fileName), openedByClient, fileContent, scriptKind);
}
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
let info = this.filenameToScriptInfo.get(fileName);
if (!info) {
let content: string;
@ -879,12 +905,37 @@ namespace ts.server {
// the file to the open, referenced file list.
const openFileRoots: ScriptInfo[] = [];
for (const rootFile of this.openFileRoots) {
let inConfiguredProject = false;
let inExternalProject = false;
for (const p of rootFile.containingProjects) {
inConfiguredProject = inConfiguredProject || p.projectKind === ProjectKind.Configured;
inExternalProject = inExternalProject || p.projectKind === ProjectKind.External;
}
if (inConfiguredProject || inExternalProject) {
const inferredProjects = rootFile.containingProjects.filter(p => p.projectKind === ProjectKind.Inferred);
for (const p of inferredProjects) {
this.removeProject(p);
}
if (inConfiguredProject) {
this.openFileRootsConfigured.push(rootFile);
}
}
else {
//
openFileRoots.push(rootFile);
}
if (rootFile.containingProjects.some(p => p.projectKind !== ProjectKind.Inferred)) {
// file was included in non-inferred project - drop old inferred project
let inInferredProjectOnly = true;
}
else {
openFileRoots.push(rootFile);
}
let inferredProjectsToRemove: Project[];
for (const p of rootFile.containingProjects) {
if (p.projectKind !== ProjectKind.Inferred) {
// file was included in non-inferred project - drop old inferred project
inInferredProjectOnly = false;
infe
break;
}
}
@ -892,6 +943,7 @@ namespace ts.server {
openFileRoots.push(rootFile);
}
else {
}
// const rootedProject = rootFile.defaultProject;
@ -941,17 +993,20 @@ namespace ts.server {
* @param filename is absolute pathname
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
openClientFile(uncheckedFileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } {
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } {
return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind);
}
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } {
let configFileName: string;
let configFileErrors: Diagnostic[];
const fileName = toNormalizedPath(uncheckedFileName);
if (!this.findContainingExternalProject(fileName)) {
({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName));
}
// at this point if file is the part of some configured/external project then this project should be created
const info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ true, fileContent, scriptKind);
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind);
this.addOpenFile(info);
this.printProjects();
return { configFileName, configFileErrors };
@ -970,27 +1025,23 @@ namespace ts.server {
this.printProjects();
}
// getProjectForFile(filename: string) {
// const scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
// if (scriptInfo) {
// return scriptInfo.defaultProject;
// }
// }
getDefaultProjectForFile(fileName: NormalizedPath) {
const scriptInfo = this.filenameToScriptInfo.get(fileName);
return scriptInfo && scriptInfo.getDefaultProject();
}
private addExternalProjectFilesForVersionedProjects(knownProjects: protocol.ExternalProjectInfo[], projects: VersionedProject[], result: protocol.ExternalProjectFiles[]): void {
private syncExternalFilesList(knownProjects: protocol.ProjectVersionInfo[], projects: Project[], result: protocol.ProjectFiles[]): void {
for (const proj of projects) {
const knownProject = ts.forEach(knownProjects, p => p.projectName === proj.getProjectName() && p);
result.push(proj.getChangesSinceVersion(knownProject && knownProject.version));
}
}
synchronizeProjectList(knownProjects: protocol.ExternalProjectInfo[]): protocol.ExternalProjectFiles[] {
const files: protocol.ExternalProjectFiles[] = [];
this.addExternalProjectFilesForVersionedProjects(knownProjects, this.externalProjects, files);
this.addExternalProjectFilesForVersionedProjects(knownProjects, this.configuredProjects, files);
for (const inferredProject of this.inferredProjects) {
files.push({ files: inferredProject.getFileNames() });
}
synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): protocol.ProjectFiles[] {
const files: protocol.ProjectFiles[] = [];
this.syncExternalFilesList(knownProjects, this.externalProjects, files);
this.syncExternalFilesList(knownProjects, this.configuredProjects, files);
this.syncExternalFilesList(knownProjects, this.inferredProjects, files);
return files;
}
@ -998,7 +1049,7 @@ namespace ts.server {
for (const file of openFiles) {
const scriptInfo = this.getScriptInfo(file.fileName);
Debug.assert(!scriptInfo || !scriptInfo.isOpen);
this.openClientFile(file.fileName, file.content);
this.openClientFileWithNormalizedPath(toNormalizedPath(file.fileName), file.content);
}
for (const file of changedFiles) {
@ -1042,7 +1093,7 @@ namespace ts.server {
openExternalProject(proj: protocol.ExternalProject): void {
const externalProject = this.findExternalProjectByProjectName(proj.projectFileName);
if (externalProject) {
this.updateVersionedProjectWorker(externalProject, proj.rootFiles, proj.options);
this.updateNonInferredProject(externalProject, proj.rootFiles, proj.options);
}
else {
let tsConfigFiles: NormalizedPath[];

View File

@ -1,4 +1,5 @@
/// <reference path="..\services\services.ts" />
/// <reference path="utilities.ts"/>
/// <reference path="scriptInfo.ts"/>
/// <reference path="lshost.ts"/>
@ -11,13 +12,33 @@ namespace ts.server {
export abstract class Project {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
private readonly rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
private lsHost: ServerLanguageServiceHost;
protected program: ts.Program;
private version = 0;
private program: ts.Program;
languageService: LanguageService;
/**
* Set of files that was returned from the last call to getChangesSinceVersion.
*/
private lastReportedFileNames: Map<string>;
/**
* Last version that was reported.
*/
private lastReportedVersion = 0;
/**
* Current project structure version.
* This property is changed in 'updateGraph' based on the set of files in program
*/
private projectStructureVersion = 0;
/**
* Current version of the project state. It is changed when:
* - new root file was added/removed
* - edit happen in some file that is currently included in the project.
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
*/
private projectStateVersion = 0;
constructor(
readonly projectKind: ProjectKind,
readonly projectService: ProjectService,
@ -46,7 +67,7 @@ namespace ts.server {
}
getProjectVersion() {
return this.version.toString();
return this.projectStateVersion.toString();
}
enableLanguageService() {
@ -68,8 +89,8 @@ namespace ts.server {
close() {
for (const fileName of this.getFileNames()) {
const info = this.projectKind.getScriptInfoForNormalizedPath(fileName);
info.detachFromProject(project);
const info = this.projectService.getScriptInfoForNormalizedPath(fileName);
info.detachFromProject(this);
}
// signal language service to release files acquired from document registry
this.languageService.dispose();
@ -85,6 +106,10 @@ namespace ts.server {
}
getFileNames() {
if (!this.program) {
return [];
}
if (!this.languageServiceEnabled) {
// if language service is disabled assume that all files in program are root files + default library
let rootFiles = this.getRootFiles();
@ -128,16 +153,18 @@ namespace ts.server {
}
}
removeFile(info: ScriptInfo) {
removeFile(info: ScriptInfo, detachFromProject: boolean = true) {
if (!this.removeRoot(info)) {
this.removeReferencedFile(info)
}
info.detachFromProject(this);
if (detachFromProject) {
info.detachFromProject(this);
}
this.markAsDirty();
}
markAsDirty() {
this.version++;
this.projectStateVersion++;
}
// remove a root file from project
@ -153,21 +180,36 @@ namespace ts.server {
private removeReferencedFile(info: ScriptInfo) {
this.lsHost.removeReferencedFile(info)
this.updateGraph();
}
updateGraph() {
if (!this.languageServiceEnabled) {
return;
}
const oldProgram = this.program;
this.program = this.languageService.getProgram();
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
if (!oldProgram || (this.program !== oldProgram && !oldProgram.structureIsReused)) {
this.projectStructureVersion++;
}
}
getScriptInfo(uncheckedFileName: string) {
const scriptInfo = this.projectService.getOrCreateScriptInfo(toNormalizedPath(uncheckedFileName), /*openedByClient*/ false);
if (scriptInfo.attachToProject(this)) {
getScriptInfoFromNormalizedPath(fileName: NormalizedPath) {
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
if (scriptInfo && scriptInfo.attachToProject(this)) {
this.markAsDirty();
}
return scriptInfo;
}
getScriptInfo(uncheckedFileName: string) {
return this.getScriptInfoFromNormalizedPath(toNormalizedPath(uncheckedFileName));
}
filesToString() {
if (!this.program) {
return "";
@ -197,19 +239,66 @@ namespace ts.server {
}
}
reloadScript(filename: string, tmpfilename: string, cb: () => void) {
const script = this.getScriptInfo(filename);
reloadScript(filename: NormalizedPath, cb: () => void) {
const script = this.getScriptInfoFromNormalizedPath(filename);
if (script) {
script.reloadFromFile(filename, cb);
}
}
getChangesSinceVersion(lastKnownVersion?: number): protocol.ProjectFiles {
const info = {
projectName: this.getProjectName(),
version: this.projectStructureVersion,
isInferred: this.projectKind === ProjectKind.Inferred
};
// check if requested version is the same that we have reported last time
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
// if current structure version is the same - return info witout any changes
if (this.projectStructureVersion == this.lastReportedVersion) {
return { info };
}
// compute and return the difference
const lastReportedFileNames = this.lastReportedFileNames;
const currentFiles = arrayToMap(this.getFileNames(), x => x);
const added: string[] = [];
const removed: string[] = [];
for (const id in currentFiles) {
if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) {
added.push(id);
}
}
for (const id in lastReportedFileNames) {
if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) {
removed.push(id);
}
}
this.lastReportedFileNames = currentFiles;
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectStructureVersion;
return { info, changes: { added, removed } };
}
else {
// unknown version - return everything
const projectFileNames = this.getFileNames();
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
this.lastReportedVersion = this.projectStructureVersion;
return { info, files: projectFileNames };
}
}
}
export class InferredProject extends Project {
static NextId = 0;
private static NextId = 1;
/**
* Unique name that identifies this particular inferred project
*/
private readonly inferredProjectName: string;
readonly inferredProjectName;
// Used to keep track of what directories are watched for this project
directoriesWatchedForTsconfig: string[] = [];
@ -237,73 +326,14 @@ namespace ts.server {
}
}
export abstract class VersionedProject extends Project {
private lastReportedFileNames: Map<string>;
private lastReportedVersion: number = 0;
currentVersion: number = 1;
updateGraph() {
if (!this.languageServiceEnabled) {
return;
}
const oldProgram = this.program;
super.updateGraph();
if (!oldProgram || !oldProgram.structureIsReused) {
this.currentVersion++;
}
}
getChangesSinceVersion(lastKnownVersion?: number): protocol.ExternalProjectFiles {
const info = {
projectName: this.getProjectName(),
version: this.currentVersion
};
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
if (this.currentVersion == this.lastReportedVersion) {
return { info };
}
const lastReportedFileNames = this.lastReportedFileNames;
const currentFiles = arrayToMap(this.getFileNames(), x => x);
const added: string[] = [];
const removed: string[] = [];
for (const id in currentFiles) {
if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) {
added.push(id);
}
}
for (const id in lastReportedFileNames) {
if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) {
removed.push(id);
}
}
this.lastReportedFileNames = currentFiles;
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.currentVersion;
return { info, changes: { added, removed } };
}
else {
// unknown version - return everything
const projectFileNames = this.getFileNames();
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
this.lastReportedVersion = this.currentVersion;
return { info, files: projectFileNames };
}
}
}
export class ConfiguredProject extends VersionedProject {
export class ConfiguredProject extends Project {
private projectFileWatcher: FileWatcher;
private directoryWatcher: FileWatcher;
private directoriesWatchedForWildcards: Map<FileWatcher>;
/** Used for configured projects which may have multiple open roots */
openRefCount = 0;
constructor(readonly configFileName: string,
constructor(readonly configFileName: NormalizedPath,
projectService: ProjectService,
documentRegistry: ts.DocumentRegistry,
hasExplicitListOfFiles: boolean,
@ -380,7 +410,7 @@ namespace ts.server {
}
}
export class ExternalProject extends VersionedProject {
export class ExternalProject extends Project {
constructor(readonly externalProjectName: string,
projectService: ProjectService,
documentRegistry: ts.DocumentRegistry,

View File

@ -494,12 +494,13 @@ declare namespace ts.server.protocol {
options: CompilerOptions;
}
export interface ExternalProjectInfo {
export interface ProjectVersionInfo {
projectName: string;
isInferred: boolean;
version: number;
}
export interface ExternalProjectChanges {
export interface ProjectChanges {
added: string[];
removed: string[];
}
@ -511,10 +512,10 @@ declare namespace ts.server.protocol {
* if changes is set - then this is the set of changes that should be applied to existing project
* otherwise - assume that nothing is changed
*/
export interface ExternalProjectFiles {
info?: ExternalProjectInfo;
export interface ProjectFiles {
info?: ProjectVersionInfo;
files?: string[];
changes?: ExternalProjectChanges;
changes?: ProjectChanges;
}
/**
@ -674,7 +675,7 @@ declare namespace ts.server.protocol {
}
export interface SynchronizeProjectListRequestArgs {
knownProjects: protocol.ExternalProjectInfo[];
knownProjects: protocol.ProjectVersionInfo[];
}
export interface ApplyChangedToOpenFilesRequest extends Request {

View File

@ -37,17 +37,13 @@ namespace ts.server {
}
detachFromProject(project: Project) {
const index = this.containingProjects.indexOf(project);
if (index < 0) {
// TODO: (assert?) attempt to detach file from project that didn't include this file
return;
}
removeItemFromSet(this.containingProjects, project);
}
detachAllProjects() {
for (const p of this.containingProjects) {
p.removeFile(this);
// detach is unnecessary since we'll clean the list of containing projects anyways
p.removeFile(this, /*detachFromProjects*/ false);
}
this.containingProjects.length = 0;
}

View File

@ -397,7 +397,7 @@ namespace ts.server {
private getDiagnosticsWorker(args: protocol.FileRequestArgs, selector: (project: Project, file: string) => Diagnostic[]) {
const { project, file } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfo(file);
const scriptInfo = project.getScriptInfoFromNormalizedPath(file);
const diagnostics = selector(project, file);
return this.convertDiagnostics(diagnostics, scriptInfo);
}
@ -436,15 +436,10 @@ namespace ts.server {
}
}
private getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
throw Errors.NoProject;
}
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
private getTypeDefinition(args: protocol.FileLocationRequestArgs): protocol.FileSpan[] {
const { file, project } = this.getFileAndProject(args)
const scriptInfo = project.getScriptInfoFromNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const definitions = project.languageService.getTypeDefinitionAtPosition(file, position);
if (!definitions) {
@ -461,18 +456,12 @@ namespace ts.server {
});
}
private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
fileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(fileName);
private getOccurrences(args: protocol.FileLocationRequestArgs): protocol.OccurrencesResponseItem[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoFromNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
if (!project) {
throw Errors.NoProject;
}
const scriptInfo = project.getScriptInfo(fileName);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const occurrences = project.languageService.getOccurrencesAtPosition(fileName, position);
const occurrences = project.languageService.getOccurrencesAtPosition(file, position);
if (!occurrences) {
return undefined;
@ -493,17 +482,11 @@ namespace ts.server {
}
private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): protocol.DocumentHighlightsItem[] | DocumentHighlights[] {
const fileName = ts.normalizePath(args.file);
const project = this.projectService.getProjectForFile(fileName);
if (!project) {
throw Errors.NoProject;
}
const scriptInfo = project.getScriptInfo(fileName);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoFromNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, args.filesToSearch);
const documentHighlights = project.languageService.getDocumentHighlights(file, position, args.filesToSearch);
if (!documentHighlights) {
return undefined;
@ -534,27 +517,23 @@ namespace ts.server {
}
}
private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo {
fileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(fileName);
if (!project) {
throw Errors.NoProject;
}
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList);
}
const projectInfo: protocol.ProjectInfo = {
configFileName: project.getProjectFileName(),
languageServiceDisabled: !project.languageServiceEnabled
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean) {
const { file, project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*errorOnMissingProject*/ true);
const projectInfo = {
configFileName: project.getProjectName(),
languageServiceDisabled: !project.languageServiceEnabled,
fileNames: needFileNameList ? project.getFileNames() : undefined
};
if (needFileNameList) {
projectInfo.fileNames = project.getFileNames();
}
return projectInfo;
}
private getRenameInfo(args: protocol.FileLocationRequestArgs) {
const { file, project } = this.getFileAndProject(args.file);
const scriptInfo = project.getScriptInfo(file);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoFromNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
return project.languageService.getRenameInfo(file, position);
}
@ -568,10 +547,10 @@ namespace ts.server {
}
}
else {
const file = normalizePath(args.file);
const info = this.projectService.getScriptInfo(file);
projects = this.projectService.findReferencingProjects(info);
const scriptInfo = this.projectService.getScriptInfo(args.file);
projects = scriptInfo.containingProjects;
}
// ts.filter handles case when 'projects' is undefined
projects = filter(projects, p => p.languageServiceEnabled);
if (!projects || !projects.length) {
throw Errors.NoProject;
@ -756,9 +735,8 @@ namespace ts.server {
* @param fileName is the name of the file to be opened
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
*/
private openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind) {
const file = ts.normalizePath(fileName);
const { configFileName, configFileErrors } = this.projectService.openClientFile(file, fileContent, scriptKind);
private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind) {
const { configFileName, configFileErrors } = this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind);
if (configFileErrors) {
this.configFileDiagnosticEvent(fileName, configFileName, configFileErrors);
}
@ -768,10 +746,14 @@ namespace ts.server {
return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
}
private getFileAndProject(args: protocol.FileLocationRequestArgs) {
const file = ts.normalizePath(args.file);
const project: Project = this.getProject(args.projectFileName) || this.projectService.getProjectForFile(file);
if (!project) {
private getFileAndProject(args: protocol.FileRequestArgs, errorOnMissingProject = true) {
return this.getFileAndProjectWorker(args.file, args.projectFileName, errorOnMissingProject);
}
private getFileAndProjectWorker(uncheckedFileName: string, projectFileName: string, errorOnMissingProject: boolean) {
const file = toNormalizedPath(uncheckedFileName);
const project: Project = this.getProject(projectFileName) || this.projectService.getDefaultProjectForFile(file);
if (!project && errorOnMissingProject) {
throw Errors.NoProject;
}
return { file, project };
@ -844,16 +826,12 @@ namespace ts.server {
}
}
private getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
throw Errors.NoProject;
}
private getFormattingEditsForRange(args: protocol.FormatRequestArgs): protocol.CodeEdit[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfo(file);
const startPosition = scriptInfo.lineOffsetToPosition(line, offset);
const endPosition = scriptInfo.lineOffsetToPosition(endLine, endOffset);
const startPosition = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
// TODO: avoid duplicate code (with formatonkey)
const edits = project.languageService.getFormattingEditsForRange(file, startPosition, endPosition,
@ -886,18 +864,12 @@ namespace ts.server {
return project.languageService.getFormattingEditsAfterKeystroke(file, args.position, args.key, args.options);
}
private getFormattingEditsAfterKeystroke(line: number, offset: number, key: string, fileName: string): protocol.CodeEdit[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
throw Errors.NoProject;
}
private getFormattingEditsAfterKeystroke(args: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfo(file);
const position = scriptInfo.lineOffsetToPosition(line, offset);
const position = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const formatOptions = this.projectService.getFormatCodeOptions(file);
const edits = project.languageService.getFormattingEditsAfterKeystroke(file, position, key,
const edits = project.languageService.getFormattingEditsAfterKeystroke(file, position, args.key,
formatOptions);
// Check whether we should auto-indent. This will be when
// the position is on a line containing only whitespace.
@ -905,8 +877,8 @@ namespace ts.server {
// getFormattingEditsAfterKeystroke either empty or pertaining
// only to the previous line. If all this is true, then
// add edits necessary to properly indent the current line.
if ((key == "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const lineInfo = scriptInfo.getLineInfo(line);
if ((args.key == "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const lineInfo = scriptInfo.getLineInfo(args.line);
if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) {
const lineText = lineInfo.leaf.text;
if (lineText.search("\\S") < 0) {
@ -1023,10 +995,9 @@ namespace ts.server {
}
private getDiagnostics(delay: number, fileNames: string[]) {
const checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => {
fileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(fileName);
const checkList = fileNames.reduce((accum: PendingErrorCheck[], uncheckedFileName: string) => {
const fileName = toNormalizedPath(uncheckedFileName);
const project = this.projectService.getDefaultProjectForFile(fileName);
if (project) {
accum.push({ fileName, project });
}
@ -1038,39 +1009,37 @@ namespace ts.server {
}
}
private change(line: number, offset: number, endLine: number, endOffset: number, insertString: string, fileName: string) {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
private change(args: protocol.ChangeRequestArgs) {
const { file, project } = this.getFileAndProject(args, /*errorOnMissingProject*/ false);
if (project) {
const scriptInfo = project.getScriptInfo(file);
const start = scriptInfo.lineOffsetToPosition(line, offset);
const end = scriptInfo.lineOffsetToPosition(endLine, endOffset);
const start = scriptInfo.lineOffsetToPosition(args.line, args.offset);
const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
if (start >= 0) {
scriptInfo.editContent(start, end, insertString);
scriptInfo.editContent(start, end, args.insertString);
this.changeSeq++;
}
this.updateProjectStructure(this.changeSeq, (n) => n === this.changeSeq);
}
}
private reload(fileName: string, tempFileName: string, reqSeq = 0) {
const file = ts.normalizePath(fileName);
const tmpfile = ts.normalizePath(tempFileName);
const project = this.projectService.getProjectForFile(file);
private reload(args: protocol.ReloadRequestArgs, reqSeq: number) {
const file = toNormalizedPath(args.file);
const project = this.projectService.getDefaultProjectForFile(file);
if (project) {
this.changeSeq++;
// make sure no changes happen before this one is finished
project.reloadScript(file, tmpfile, () => {
project.reloadScript(file, () => {
this.output(undefined, CommandNames.Reload, reqSeq);
});
}
}
private saveToTmp(fileName: string, tempFileName: string) {
const file = ts.normalizePath(fileName);
const file = toNormalizedPath(fileName);
const tmpfile = ts.normalizePath(tempFileName);
const project = this.projectService.getProjectForFile(file);
const project = this.projectService.getDefaultProjectForFile(file);
if (project) {
project.saveTo(file, tmpfile);
}
@ -1084,12 +1053,12 @@ namespace ts.server {
this.projectService.closeClientFile(file);
}
private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] {
private decorateNavigationBarItem(project: Project, fileName: NormalizedPath, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] {
if (!items) {
return undefined;
}
const scriptInfo = project.getScriptInfo(fileName);
const scriptInfo = project.getScriptInfoFromNormalizedPath(fileName);
return items.map(item => ({
text: item.text,
@ -1104,16 +1073,15 @@ namespace ts.server {
}));
}
private getNavigationBarItems(fileName: string, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] {
const { file, project } = this.getFileAndProject(fileName);
private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] {
const { file, project } = this.getFileAndProject(args);
const items = project.languageService.getNavigationBarItems(file);
if (!items) {
return undefined;
}
return simplifiedResult
? this.decorateNavigationBarItem(project, fileName, items)
? this.decorateNavigationBarItem(project, file, items)
: items;
}
@ -1197,7 +1165,7 @@ namespace ts.server {
}
private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] {
const { file, project } = this.getFileAndProject(args.file);
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfo(file);
const position = this.getPosition(args, scriptInfo);
@ -1219,7 +1187,7 @@ namespace ts.server {
}
getDiagnosticsForProject(delay: number, fileName: string) {
const { fileNames, languageServiceDisabled } = this.getProjectInfo(fileName, /*needFileNameList*/ true);
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true);
if (languageServiceDisabled) {
return;
}
@ -1228,12 +1196,12 @@ namespace ts.server {
let fileNamesInProject = fileNames.filter((value, index, array) => value.indexOf("lib.d.ts") < 0);
// Sort the file name list to make the recently touched files come first
const highPriorityFiles: string[] = [];
const mediumPriorityFiles: string[] = [];
const lowPriorityFiles: string[] = [];
const veryLowPriorityFiles: string[] = [];
const normalizedFileName = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(normalizedFileName);
const highPriorityFiles: NormalizedPath[] = [];
const mediumPriorityFiles: NormalizedPath[] = [];
const lowPriorityFiles: NormalizedPath[] = [];
const veryLowPriorityFiles: NormalizedPath[] = [];
const normalizedFileName = toNormalizedPath(fileName);
const project = this.projectService.getDefaultProjectForFile(normalizedFileName);
for (const fileNameInProject of fileNamesInProject) {
if (this.getCanonicalFileName(fileNameInProject) == this.getCanonicalFileName(fileName))
highPriorityFiles.push(fileNameInProject);
@ -1253,10 +1221,7 @@ namespace ts.server {
fileNamesInProject = highPriorityFiles.concat(mediumPriorityFiles).concat(lowPriorityFiles).concat(veryLowPriorityFiles);
if (fileNamesInProject.length > 0) {
const checkList = fileNamesInProject.map<PendingErrorCheck>((fileName: string) => {
const normalizedFileName = ts.normalizePath(fileName);
return { fileName: normalizedFileName, project };
});
const checkList = fileNamesInProject.map(fileName => ({ fileName, project }));
// Project level error analysis runs on background files too, therefore
// doesn't require the file to be opened
this.updateErrorCheck(checkList, this.changeSeq, (n) => n == this.changeSeq, delay, 200, /*requireOpen*/ false);
@ -1316,9 +1281,8 @@ namespace ts.server {
[CommandNames.DefinitionFull]: (request: protocol.DefinitionRequest) => {
return this.requiredResponse(this.getDefinition(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.TypeDefinition]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return this.requiredResponse(this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file));
[CommandNames.TypeDefinition]: (request: protocol.FileLocationRequest) => {
return this.requiredResponse(this.getTypeDefinition(request.arguments));
},
[CommandNames.References]: (request: protocol.FileLocationRequest) => {
return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ true));
@ -1352,7 +1316,7 @@ namespace ts.server {
scriptKind = ScriptKind.JSX;
break;
}
this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind);
this.openClientFile(toNormalizedPath(openArgs.file), openArgs.fileContent, scriptKind);
return this.notRequired();
},
[CommandNames.Quickinfo]: (request: protocol.QuickInfoRequest) => {
@ -1382,13 +1346,11 @@ namespace ts.server {
[CommandNames.DocCommentTemplate]: (request: protocol.FileLocationRequest) => {
return this.requiredResponse(this.getDocCommentTemplate(request.arguments));
},
[CommandNames.Format]: (request: protocol.Request) => {
const formatArgs = <protocol.FormatRequestArgs>request.arguments;
return this.requiredResponse(this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file));
[CommandNames.Format]: (request: protocol.FormatRequest) => {
return this.requiredResponse(this.getFormattingEditsForRange(request.arguments));
},
[CommandNames.Formatonkey]: (request: protocol.Request) => {
const formatOnKeyArgs = <protocol.FormatOnKeyRequestArgs>request.arguments;
return this.requiredResponse(this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file));
[CommandNames.Formatonkey]: (request: protocol.FormatOnKeyRequest) => {
return this.requiredResponse(this.getFormattingEditsAfterKeystroke(request.arguments));
},
[CommandNames.FormatFull]: (request: protocol.FormatRequest) => {
return this.requiredResponse(this.getFormattingEditsForDocumentFull(request.arguments));
@ -1438,32 +1400,29 @@ namespace ts.server {
const { file, delay } = <protocol.GeterrForProjectRequestArgs>request.arguments;
return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false };
},
[CommandNames.Change]: (request: protocol.Request) => {
const changeArgs = <protocol.ChangeRequestArgs>request.arguments;
this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset,
changeArgs.insertString, changeArgs.file);
return { responseRequired: false };
[CommandNames.Change]: (request: protocol.ChangeRequest) => {
this.change(request.arguments);
return this.notRequired();
},
[CommandNames.Configure]: (request: protocol.Request) => {
const configureArgs = <protocol.ConfigureRequestArguments>request.arguments;
this.projectService.setHostConfiguration(configureArgs);
this.output(undefined, CommandNames.Configure, request.seq);
return { responseRequired: false };
return this.notRequired();
},
[CommandNames.Reload]: (request: protocol.Request) => {
const reloadArgs = <protocol.ReloadRequestArgs>request.arguments;
this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq);
return { response: { reloadFinished: true }, responseRequired: true };
[CommandNames.Reload]: (request: protocol.ReloadRequest) => {
this.reload(request.arguments, request.seq);
return this.requiredResponse({ reloadFinished: true });
},
[CommandNames.Saveto]: (request: protocol.Request) => {
const savetoArgs = <protocol.SavetoRequestArgs>request.arguments;
this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
return { responseRequired: false };
return this.notRequired();
},
[CommandNames.Close]: (request: protocol.Request) => {
const closeArgs = <protocol.FileRequestArgs>request.arguments;
this.closeClientFile(closeArgs.file);
return { responseRequired: false };
return this.notRequired();
},
[CommandNames.Navto]: (request: protocol.NavtoRequest) => {
return this.requiredResponse(this.getNavigateToItems(request.arguments, /*simplifiedResult*/ true));
@ -1478,14 +1437,13 @@ namespace ts.server {
return this.requiredResponse(this.getBraceMatching(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.NavBar]: (request: protocol.FileRequest) => {
return this.requiredResponse(this.getNavigationBarItems(request.arguments.file, /*simplifiedResult*/ true));
return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.NavBarFull]: (request: protocol.FileRequest) => {
return this.requiredResponse(this.getNavigationBarItems(request.arguments.file, /*simplifiedResult*/ false));
return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.Occurrences]: (request: protocol.Request) => {
const { line, offset, file: fileName } = <protocol.FileLocationRequestArgs>request.arguments;
return { response: this.getOccurrences(line, offset, fileName), responseRequired: true };
[CommandNames.Occurrences]: (request: protocol.FileLocationRequest) => {
return this.requiredResponse(this.getOccurrences(request.arguments));;
},
[CommandNames.DocumentHighlights]: (request: protocol.DocumentHighlightsRequest) => {
return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ true));
@ -1493,13 +1451,12 @@ namespace ts.server {
[CommandNames.DocumentHighlightsFull]: (request: protocol.DocumentHighlightsRequest) => {
return this.requiredResponse(this.getDocumentHighlights(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
const { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true };
[CommandNames.ProjectInfo]: (request: protocol.ProjectInfoRequest) => {
return this.requiredResponse(this.getProjectInfo(request.arguments));
},
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
this.reloadProjects();
return { responseRequired: false };
return this.notRequired();
}
};
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {

View File

@ -1,5 +1,4 @@
/// <reference path="..\services\services.ts" />
/// <reference path="session.ts" />
namespace ts.server {
export interface Logger {
@ -42,11 +41,14 @@ namespace ts.server {
}
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
if (items.length === 0) {
return;
}
const index = items.indexOf(itemToRemove);
if (index < 0) {
return;
}
if (items.length === 0) {
if (items.length === 1) {
items.pop();
}
else {

View File

@ -118,7 +118,7 @@ namespace ts {
const newContent = `import {x} from "f1"
var x: string = 1;`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
rootScriptInfo.editContent(0, root.content.length, newContent);
// trigger synchronization to make sure that import will be fetched from the cache
diags = project.languageService.getSemanticDiagnostics(imported.name);
// ensure file has correct number of errors after edit
@ -135,7 +135,7 @@ namespace ts {
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f2"`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
rootScriptInfo.editContent(0, root.content.length, newContent);
try {
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
@ -160,7 +160,7 @@ namespace ts {
};
const newContent = `import {x} from "f1"`;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, newContent);
rootScriptInfo.editContent(0, root.content.length, newContent);
project.languageService.getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
@ -213,7 +213,7 @@ namespace ts {
// assert that import will success once file appear on disk
fileMap[imported.name] = imported;
fileExistsCalledForBar = false;
rootScriptInfo.editContent(0, rootScriptInfo.content.length, `import {y} from "bar"`);
rootScriptInfo.editContent(0, root.content.length, `import {y} from "bar"`);
diags = project.languageService.getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");