Skip to content

Commit c8d37dc

Browse files
committed
[in progress] project system work - versions
1 parent c9b82ed commit c8d37dc

7 files changed

Lines changed: 350 additions & 313 deletions

File tree

src/server/editorServices.ts

Lines changed: 129 additions & 78 deletions
Large diffs are not rendered by default.

src/server/project.ts

Lines changed: 105 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/// <reference path="..\services\services.ts" />
2+
/// <reference path="utilities.ts"/>
23
/// <reference path="scriptInfo.ts"/>
34
/// <reference path="lshost.ts"/>
45

@@ -11,13 +12,33 @@ namespace ts.server {
1112

1213
export abstract class Project {
1314
private rootFiles: ScriptInfo[] = [];
14-
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
15+
private readonly rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
1516
private lsHost: ServerLanguageServiceHost;
16-
protected program: ts.Program;
17-
private version = 0;
17+
private program: ts.Program;
1818

1919
languageService: LanguageService;
2020

21+
/**
22+
* Set of files that was returned from the last call to getChangesSinceVersion.
23+
*/
24+
private lastReportedFileNames: Map<string>;
25+
/**
26+
* Last version that was reported.
27+
*/
28+
private lastReportedVersion = 0;
29+
/**
30+
* Current project structure version.
31+
* This property is changed in 'updateGraph' based on the set of files in program
32+
*/
33+
private projectStructureVersion = 0;
34+
/**
35+
* Current version of the project state. It is changed when:
36+
* - new root file was added/removed
37+
* - edit happen in some file that is currently included in the project.
38+
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
39+
*/
40+
private projectStateVersion = 0;
41+
2142
constructor(
2243
readonly projectKind: ProjectKind,
2344
readonly projectService: ProjectService,
@@ -46,7 +67,7 @@ namespace ts.server {
4667
}
4768

4869
getProjectVersion() {
49-
return this.version.toString();
70+
return this.projectStateVersion.toString();
5071
}
5172

5273
enableLanguageService() {
@@ -68,8 +89,8 @@ namespace ts.server {
6889

6990
close() {
7091
for (const fileName of this.getFileNames()) {
71-
const info = this.projectKind.getScriptInfoForNormalizedPath(fileName);
72-
info.detachFromProject(project);
92+
const info = this.projectService.getScriptInfoForNormalizedPath(fileName);
93+
info.detachFromProject(this);
7394
}
7495
// signal language service to release files acquired from document registry
7596
this.languageService.dispose();
@@ -85,6 +106,10 @@ namespace ts.server {
85106
}
86107

87108
getFileNames() {
109+
if (!this.program) {
110+
return [];
111+
}
112+
88113
if (!this.languageServiceEnabled) {
89114
// if language service is disabled assume that all files in program are root files + default library
90115
let rootFiles = this.getRootFiles();
@@ -128,16 +153,18 @@ namespace ts.server {
128153
}
129154
}
130155

131-
removeFile(info: ScriptInfo) {
156+
removeFile(info: ScriptInfo, detachFromProject: boolean = true) {
132157
if (!this.removeRoot(info)) {
133158
this.removeReferencedFile(info)
134159
}
135-
info.detachFromProject(this);
160+
if (detachFromProject) {
161+
info.detachFromProject(this);
162+
}
136163
this.markAsDirty();
137164
}
138165

139166
markAsDirty() {
140-
this.version++;
167+
this.projectStateVersion++;
141168
}
142169

143170
// remove a root file from project
@@ -153,21 +180,36 @@ namespace ts.server {
153180

154181
private removeReferencedFile(info: ScriptInfo) {
155182
this.lsHost.removeReferencedFile(info)
156-
this.updateGraph();
157183
}
158184

159185
updateGraph() {
186+
if (!this.languageServiceEnabled) {
187+
return;
188+
}
189+
190+
const oldProgram = this.program;
160191
this.program = this.languageService.getProgram();
192+
193+
// bump up the version if
194+
// - oldProgram is not set - this is a first time updateGraph is called
195+
// - newProgram is different from the old program and structure of the old program was not reused.
196+
if (!oldProgram || (this.program !== oldProgram && !oldProgram.structureIsReused)) {
197+
this.projectStructureVersion++;
198+
}
161199
}
162200

163-
getScriptInfo(uncheckedFileName: string) {
164-
const scriptInfo = this.projectService.getOrCreateScriptInfo(toNormalizedPath(uncheckedFileName), /*openedByClient*/ false);
165-
if (scriptInfo.attachToProject(this)) {
201+
getScriptInfoFromNormalizedPath(fileName: NormalizedPath) {
202+
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
203+
if (scriptInfo && scriptInfo.attachToProject(this)) {
166204
this.markAsDirty();
167205
}
168206
return scriptInfo;
169207
}
170208

209+
getScriptInfo(uncheckedFileName: string) {
210+
return this.getScriptInfoFromNormalizedPath(toNormalizedPath(uncheckedFileName));
211+
}
212+
171213
filesToString() {
172214
if (!this.program) {
173215
return "";
@@ -197,74 +239,26 @@ namespace ts.server {
197239
}
198240
}
199241

200-
reloadScript(filename: string, tmpfilename: string, cb: () => void) {
201-
const script = this.getScriptInfo(filename);
242+
reloadScript(filename: NormalizedPath, cb: () => void) {
243+
const script = this.getScriptInfoFromNormalizedPath(filename);
202244
if (script) {
203245
script.reloadFromFile(filename, cb);
204246
}
205247
}
206-
}
207-
208-
export class InferredProject extends Project {
209248

210-
static NextId = 0;
211-
212-
readonly inferredProjectName;
213-
// Used to keep track of what directories are watched for this project
214-
directoriesWatchedForTsconfig: string[] = [];
215-
216-
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean) {
217-
super(ProjectKind.Inferred,
218-
projectService,
219-
documentRegistry,
220-
/*files*/ undefined,
221-
languageServiceEnabled,
222-
/*compilerOptions*/ undefined);
223-
224-
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId++);
225-
}
226-
227-
getProjectName() {
228-
return this.inferredProjectName;
229-
}
230-
231-
close() {
232-
super.close();
233-
234-
for (const directory of this.directoriesWatchedForTsconfig) {
235-
this.projectService.stopWatchingDirectory(directory);
236-
}
237-
}
238-
}
239-
240-
export abstract class VersionedProject extends Project {
241-
242-
private lastReportedFileNames: Map<string>;
243-
private lastReportedVersion: number = 0;
244-
currentVersion: number = 1;
245-
246-
updateGraph() {
247-
if (!this.languageServiceEnabled) {
248-
return;
249-
}
250-
const oldProgram = this.program;
251-
252-
super.updateGraph();
253-
254-
if (!oldProgram || !oldProgram.structureIsReused) {
255-
this.currentVersion++;
256-
}
257-
}
258-
259-
getChangesSinceVersion(lastKnownVersion?: number): protocol.ExternalProjectFiles {
249+
getChangesSinceVersion(lastKnownVersion?: number): protocol.ProjectFiles {
260250
const info = {
261251
projectName: this.getProjectName(),
262-
version: this.currentVersion
252+
version: this.projectStructureVersion,
253+
isInferred: this.projectKind === ProjectKind.Inferred
263254
};
255+
// check if requested version is the same that we have reported last time
264256
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
265-
if (this.currentVersion == this.lastReportedVersion) {
257+
// if current structure version is the same - return info witout any changes
258+
if (this.projectStructureVersion == this.lastReportedVersion) {
266259
return { info };
267260
}
261+
// compute and return the difference
268262
const lastReportedFileNames = this.lastReportedFileNames;
269263
const currentFiles = arrayToMap(this.getFileNames(), x => x);
270264

@@ -283,27 +277,63 @@ namespace ts.server {
283277
this.lastReportedFileNames = currentFiles;
284278

285279
this.lastReportedFileNames = currentFiles;
286-
this.lastReportedVersion = this.currentVersion;
280+
this.lastReportedVersion = this.projectStructureVersion;
287281
return { info, changes: { added, removed } };
288282
}
289283
else {
290284
// unknown version - return everything
291285
const projectFileNames = this.getFileNames();
292286
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
293-
this.lastReportedVersion = this.currentVersion;
287+
this.lastReportedVersion = this.projectStructureVersion;
294288
return { info, files: projectFileNames };
295289
}
296290
}
297291
}
298292

299-
export class ConfiguredProject extends VersionedProject {
293+
export class InferredProject extends Project {
294+
295+
private static NextId = 1;
296+
297+
/**
298+
* Unique name that identifies this particular inferred project
299+
*/
300+
private readonly inferredProjectName: string;
301+
302+
// Used to keep track of what directories are watched for this project
303+
directoriesWatchedForTsconfig: string[] = [];
304+
305+
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean) {
306+
super(ProjectKind.Inferred,
307+
projectService,
308+
documentRegistry,
309+
/*files*/ undefined,
310+
languageServiceEnabled,
311+
/*compilerOptions*/ undefined);
312+
313+
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId++);
314+
}
315+
316+
getProjectName() {
317+
return this.inferredProjectName;
318+
}
319+
320+
close() {
321+
super.close();
322+
323+
for (const directory of this.directoriesWatchedForTsconfig) {
324+
this.projectService.stopWatchingDirectory(directory);
325+
}
326+
}
327+
}
328+
329+
export class ConfiguredProject extends Project {
300330
private projectFileWatcher: FileWatcher;
301331
private directoryWatcher: FileWatcher;
302332
private directoriesWatchedForWildcards: Map<FileWatcher>;
303333
/** Used for configured projects which may have multiple open roots */
304334
openRefCount = 0;
305335

306-
constructor(readonly configFileName: string,
336+
constructor(readonly configFileName: NormalizedPath,
307337
projectService: ProjectService,
308338
documentRegistry: ts.DocumentRegistry,
309339
hasExplicitListOfFiles: boolean,
@@ -380,7 +410,7 @@ namespace ts.server {
380410
}
381411
}
382412

383-
export class ExternalProject extends VersionedProject {
413+
export class ExternalProject extends Project {
384414
constructor(readonly externalProjectName: string,
385415
projectService: ProjectService,
386416
documentRegistry: ts.DocumentRegistry,

src/server/protocol.d.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,12 +494,13 @@ declare namespace ts.server.protocol {
494494
options: CompilerOptions;
495495
}
496496

497-
export interface ExternalProjectInfo {
497+
export interface ProjectVersionInfo {
498498
projectName: string;
499+
isInferred: boolean;
499500
version: number;
500501
}
501502

502-
export interface ExternalProjectChanges {
503+
export interface ProjectChanges {
503504
added: string[];
504505
removed: string[];
505506
}
@@ -511,10 +512,10 @@ declare namespace ts.server.protocol {
511512
* if changes is set - then this is the set of changes that should be applied to existing project
512513
* otherwise - assume that nothing is changed
513514
*/
514-
export interface ExternalProjectFiles {
515-
info?: ExternalProjectInfo;
515+
export interface ProjectFiles {
516+
info?: ProjectVersionInfo;
516517
files?: string[];
517-
changes?: ExternalProjectChanges;
518+
changes?: ProjectChanges;
518519
}
519520

520521
/**
@@ -674,7 +675,7 @@ declare namespace ts.server.protocol {
674675
}
675676

676677
export interface SynchronizeProjectListRequestArgs {
677-
knownProjects: protocol.ExternalProjectInfo[];
678+
knownProjects: protocol.ProjectVersionInfo[];
678679
}
679680

680681
export interface ApplyChangedToOpenFilesRequest extends Request {

src/server/scriptInfo.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,13 @@ namespace ts.server {
3737
}
3838

3939
detachFromProject(project: Project) {
40-
const index = this.containingProjects.indexOf(project);
41-
if (index < 0) {
42-
// TODO: (assert?) attempt to detach file from project that didn't include this file
43-
return;
44-
}
4540
removeItemFromSet(this.containingProjects, project);
4641
}
4742

4843
detachAllProjects() {
4944
for (const p of this.containingProjects) {
50-
p.removeFile(this);
45+
// detach is unnecessary since we'll clean the list of containing projects anyways
46+
p.removeFile(this, /*detachFromProjects*/ false);
5147
}
5248
this.containingProjects.length = 0;
5349
}

0 commit comments

Comments
 (0)