Skip to content

Commit 96a867a

Browse files
committed
add support for single inferred project
1 parent 15f825b commit 96a867a

9 files changed

Lines changed: 183 additions & 89 deletions

File tree

src/harness/harnessLanguageService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ namespace Harness.LanguageService {
681681
const serverHost = new SessionServerHost(clientHost);
682682
const server = new ts.server.Session(serverHost,
683683
{ isCancellationRequested: () => false },
684+
/*useOneInferredProject*/ false,
684685
Buffer ? Buffer.byteLength : (string: string, encoding?: string) => string.length,
685686
process.hrtime, serverHost);
686687

src/server/editorServices.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,25 @@ namespace ts.server {
137137

138138
private readonly directoryWatchers: DirectoryWatchers;
139139

140-
private hostConfiguration: HostConfiguration;
140+
private readonly hostConfiguration: HostConfiguration;
141141

142142
private timerForDetectingProjectFileListChanges: Map<any> = {};
143143

144144
constructor(public readonly host: ServerHost,
145145
public readonly logger: Logger,
146146
public readonly cancellationToken: HostCancellationToken,
147+
private readonly useOneInferredProject: boolean,
147148
private readonly eventHandler?: ProjectServiceEventHandler) {
148149

149150
this.directoryWatchers = new DirectoryWatchers(this);
150151
// ts.disableIncrementalParsing = true;
151-
this.setDefaultHostConfiguration();
152-
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
153-
}
154152

155-
private setDefaultHostConfiguration() {
156153
this.hostConfiguration = {
157154
formatCodeOptions: getDefaultFormatCodeSettings(this.host),
158155
hostInfo: "Unknown host"
159156
};
157+
158+
this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory());
160159
}
161160

162161
stopWatchingDirectory(directory: string) {
@@ -378,31 +377,36 @@ namespace ts.server {
378377
}
379378
if (info.containingProjects.length === 0) {
380379
// create new inferred project p with the newly opened file as root
381-
const inferredProject = this.createAndAddInferredProject(info);
382-
const openFileRoots: ScriptInfo[] = [];
383-
// for each inferred project root r
384-
for (const rootFile of this.openFileRoots) {
385-
// if r referenced by the new project
386-
if (inferredProject.containsScriptInfo(rootFile)) {
387-
// remove inferred project that was initially created for rootFile
388-
const defaultProject = rootFile.getDefaultProject();
389-
if (defaultProject === inferredProject) {
390-
continue;
391-
}
392-
Debug.assert(defaultProject.projectKind === ProjectKind.Inferred);
380+
// or add root to existing inferred project if 'useOneInferredProject' is true
381+
const inferredProject = this.addFileToInferredProject(info);
382+
if (!this.useOneInferredProject) {
383+
384+
// if useOneInferredProject is not set then try to fixup ownership of open files
385+
const openFileRoots: ScriptInfo[] = [];
386+
// for each inferred project root r
387+
for (const rootFile of this.openFileRoots) {
388+
// if r referenced by the new project
389+
if (inferredProject.containsScriptInfo(rootFile)) {
390+
// remove inferred project that was initially created for rootFile
391+
const defaultProject = rootFile.getDefaultProject();
392+
if (defaultProject === inferredProject) {
393+
continue;
394+
}
395+
Debug.assert(defaultProject.projectKind === ProjectKind.Inferred);
393396

394-
this.removeProject(defaultProject);
395-
// put r in referenced open file list
396-
this.openFilesReferenced.push(rootFile);
397-
// set default project of r to the new project
398-
rootFile.attachToProject(inferredProject);
399-
}
400-
else {
401-
// otherwise, keep r as root of inferred project
402-
openFileRoots.push(rootFile);
397+
this.removeProject(defaultProject);
398+
// put r in referenced open file list
399+
this.openFilesReferenced.push(rootFile);
400+
// set default project of r to the new project
401+
rootFile.attachToProject(inferredProject);
402+
}
403+
else {
404+
// otherwise, keep r as root of inferred project
405+
openFileRoots.push(rootFile);
406+
}
403407
}
408+
this.openFileRoots = openFileRoots;
404409
}
405-
this.openFileRoots = openFileRoots;
406410
}
407411

408412
this.openFileRoots.push(info);
@@ -736,14 +740,19 @@ namespace ts.server {
736740

737741
// delete inferred project
738742
let toRemove: Project[];
743+
744+
// TODO: unify logic
739745
for (const p of info.containingProjects) {
740746
if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) {
741747
(toRemove || (toRemove = [])).push(p);
742748
}
743749
}
744750
if (toRemove) {
745751
for (const p of toRemove) {
746-
this.removeProject(p);
752+
p.removeFile(info);
753+
if (!p.hasRoots()) {
754+
this.removeProject(p);
755+
}
747756
}
748757
}
749758
}
@@ -792,8 +801,12 @@ namespace ts.server {
792801
}
793802
}
794803

795-
createAndAddInferredProject(root: ScriptInfo) {
796-
const project = new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true);
804+
addFileToInferredProject(root: ScriptInfo) {
805+
const useExistingProject = this.useOneInferredProject && this.inferredProjects.length;
806+
const project = useExistingProject
807+
? this.inferredProjects[0]
808+
: new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true);
809+
797810
project.addRoot(root);
798811

799812
this.directoryWatchers.startWatchingContainingDirectoriesForFile(
@@ -802,7 +815,10 @@ namespace ts.server {
802815
fileName => this.onConfigFileAddedForInferredProject(fileName));
803816

804817
project.updateGraph();
805-
this.inferredProjects.push(project);
818+
819+
if (!useExistingProject) {
820+
this.inferredProjects.push(project);
821+
}
806822
return project;
807823
}
808824

@@ -966,7 +982,10 @@ namespace ts.server {
966982
if (inConfiguredProject || inExternalProject) {
967983
const inferredProjects = rootFile.containingProjects.filter(p => p.projectKind === ProjectKind.Inferred);
968984
for (const p of inferredProjects) {
969-
this.removeProject(p);
985+
p.removeFile(rootFile, /*detachFromProject*/ true);
986+
if (!p.hasRoots()) {
987+
this.removeProject(p);
988+
}
970989
}
971990
if (inConfiguredProject) {
972991
this.openFileRootsConfigured.push(rootFile);
@@ -1033,6 +1052,9 @@ namespace ts.server {
10331052
for (const f of unattachedOpenFiles) {
10341053
this.addOpenFile(f);
10351054
}
1055+
for (const p of this.inferredProjects) {
1056+
p.updateGraph();
1057+
}
10361058
this.printProjects();
10371059
}
10381060

src/server/project.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ namespace ts.server {
108108
return this.compilerOptions;
109109
}
110110

111+
hasRoots() {
112+
return this.rootFiles.length > 0;
113+
}
114+
111115
getRootFiles() {
112116
return this.rootFiles.map(info => info.fileName);
113117
}

src/server/protocol.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,11 @@ declare namespace ts.server.protocol {
606606
* The format options to use during formatting and other code editing features.
607607
*/
608608
formatOptions?: FormatOptions;
609+
610+
/**
611+
* If set to true - then all loose files will land into one inferred project
612+
*/
613+
useOneInferredProject?: boolean;
609614
}
610615

611616
/**

src/server/server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ namespace ts.server {
9191
}
9292

9393
class IOSession extends Session {
94-
constructor(host: ServerHost, cancellationToken: HostCancellationToken, logger: ts.server.Logger) {
95-
super(host, cancellationToken, Buffer.byteLength, process.hrtime, logger);
94+
constructor(host: ServerHost, cancellationToken: HostCancellationToken, useOneInferredProject: boolean, logger: ts.server.Logger) {
95+
super(host, cancellationToken, useOneInferredProject, Buffer.byteLength, process.hrtime, logger);
9696
}
9797

9898
exit() {
@@ -304,7 +304,8 @@ namespace ts.server {
304304
};
305305
};
306306

307-
const ioSession = new IOSession(sys, cancellationToken, logger);
307+
const useOneInferredProject = sys.args.some(arg => arg === "--useOneInferredProject");
308+
const ioSession = new IOSession(sys, cancellationToken, useOneInferredProject, logger);
308309
process.on("uncaughtException", function(err: Error) {
309310
ioSession.logError(err, "unknown");
310311
});

src/server/session.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,13 @@ namespace ts.server {
178178

179179
constructor(
180180
private host: ServerHost,
181-
private cancellationToken: HostCancellationToken,
181+
cancellationToken: HostCancellationToken,
182+
useOneInferredProject: boolean,
182183
private byteLength: (buf: string, encoding?: string) => number,
183184
private hrtime: (start?: number[]) => number[],
184185
private logger: Logger) {
185186
this.projectService =
186-
new ProjectService(host, logger, cancellationToken, (eventName, project, fileName) => {
187+
new ProjectService(host, logger, cancellationToken, useOneInferredProject, (eventName, project, fileName) => {
187188
this.handleEvent(eventName, project, fileName);
188189
});
189190
}
@@ -1380,10 +1381,10 @@ namespace ts.server {
13801381
this.cleanup();
13811382
return this.requiredResponse(true);
13821383
},
1383-
[CommandNames.SemanticDiagnosticsSync]: (request: protocol.FileRequest) => {
1384+
[CommandNames.SemanticDiagnosticsSync]: (request: protocol.SemanticDiagnosticsSyncRequest) => {
13841385
return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments));
13851386
},
1386-
[CommandNames.SyntacticDiagnosticsSync]: (request: protocol.FileRequest) => {
1387+
[CommandNames.SyntacticDiagnosticsSync]: (request: protocol.SyntacticDiagnosticsSyncRequest) => {
13871388
return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments));
13881389
},
13891390
[CommandNames.Geterr]: (request: protocol.Request) => {
@@ -1398,9 +1399,8 @@ namespace ts.server {
13981399
this.change(request.arguments);
13991400
return this.notRequired();
14001401
},
1401-
[CommandNames.Configure]: (request: protocol.Request) => {
1402-
const configureArgs = <protocol.ConfigureRequestArguments>request.arguments;
1403-
this.projectService.setHostConfiguration(configureArgs);
1402+
[CommandNames.Configure]: (request: protocol.ConfigureRequest) => {
1403+
this.projectService.setHostConfiguration(request.arguments);
14041404
this.output(undefined, CommandNames.Configure, request.seq);
14051405
return this.notRequired();
14061406
},

tests/cases/unittests/cachingInServerLSHost.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ namespace ts {
7979
msg: (s: string, type?: string) => { }
8080
};
8181

82-
const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false });
82+
const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }, /*useOneInferredProject*/ false);
8383
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true, /*containingProject*/ undefined);
84-
const project = projectService.createAndAddInferredProject(rootScriptInfo);
84+
const project = projectService.addFileToInferredProject(rootScriptInfo);
8585
project.setCompilerOptions({ module: ts.ModuleKind.AMD } );
8686
return {
8787
project,

tests/cases/unittests/session.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace ts.server {
4040
let lastSent: protocol.Message;
4141

4242
beforeEach(() => {
43-
session = new Session(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger);
43+
session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger);
4444
session.send = (msg: protocol.Message) => {
4545
lastSent = msg;
4646
};
@@ -265,7 +265,7 @@ namespace ts.server {
265265
lastSent: protocol.Message;
266266
customHandler = "testhandler";
267267
constructor() {
268-
super(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger);
268+
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger);
269269
this.addProtocolHandler(this.customHandler, () => {
270270
return { response: undefined, responseRequired: true };
271271
});
@@ -323,7 +323,7 @@ namespace ts.server {
323323
class InProcSession extends Session {
324324
private queue: protocol.Request[] = [];
325325
constructor(private client: InProcClient) {
326-
super(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger);
326+
super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger);
327327
this.addProtocolHandler("echo", (req: protocol.Request) => ({
328328
response: req.arguments,
329329
responseRequired: true

0 commit comments

Comments
 (0)