mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
[in progress] project system work - versions
This commit is contained in:
parent
c9b82eddda
commit
c8d37dc87e
@ -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[];
|
||||
|
||||
@ -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,
|
||||
|
||||
13
src/server/protocol.d.ts
vendored
13
src/server/protocol.d.ts
vendored
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 }) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user