Merge pull request #17602 from Microsoft/multiInferredProjects
Adds support for inferred project isolation by projectRootPath
This commit is contained in:
commit
360dc914c3
|
@ -825,6 +825,7 @@ namespace Harness.LanguageService {
|
||||||
host: serverHost,
|
host: serverHost,
|
||||||
cancellationToken: ts.server.nullCancellationToken,
|
cancellationToken: ts.server.nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: undefined,
|
typingsInstaller: undefined,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
|
|
@ -57,6 +57,7 @@ namespace ts {
|
||||||
logger: projectSystem.nullLogger,
|
logger: projectSystem.nullLogger,
|
||||||
cancellationToken: { isCancellationRequested: () => false },
|
cancellationToken: { isCancellationRequested: () => false },
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: undefined
|
typingsInstaller: undefined
|
||||||
};
|
};
|
||||||
const projectService = new server.ProjectService(svcOpts);
|
const projectService = new server.ProjectService(svcOpts);
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace ts.projectSystem {
|
||||||
host,
|
host,
|
||||||
cancellationToken: nullCancellationToken,
|
cancellationToken: nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: typingsInstaller || server.nullTypingsInstaller,
|
typingsInstaller: typingsInstaller || server.nullTypingsInstaller,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
@ -552,7 +553,7 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" });
|
const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" });
|
||||||
const typingsInstaller = createTestTypingsInstaller(host);
|
const typingsInstaller = createTestTypingsInstaller(host);
|
||||||
const session = createSession(host, typingsInstaller);
|
const session = createSession(host, { typingsInstaller });
|
||||||
|
|
||||||
openFilesForSession([file1, file2], session);
|
openFilesForSession([file1, file2], session);
|
||||||
const compileFileRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
|
const compileFileRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
|
||||||
|
|
|
@ -43,6 +43,7 @@ namespace ts.server {
|
||||||
host: mockHost,
|
host: mockHost,
|
||||||
cancellationToken: nullCancellationToken,
|
cancellationToken: nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: undefined,
|
typingsInstaller: undefined,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
@ -394,6 +395,7 @@ namespace ts.server {
|
||||||
host: mockHost,
|
host: mockHost,
|
||||||
cancellationToken: nullCancellationToken,
|
cancellationToken: nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: undefined,
|
typingsInstaller: undefined,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
@ -461,6 +463,7 @@ namespace ts.server {
|
||||||
host: mockHost,
|
host: mockHost,
|
||||||
cancellationToken: nullCancellationToken,
|
cancellationToken: nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
typingsInstaller: undefined,
|
typingsInstaller: undefined,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
|
|
@ -185,23 +185,28 @@ namespace ts.projectSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller, projectServiceEventHandler?: server.ProjectServiceEventHandler, cancellationToken?: server.ServerCancellationToken, throttleWaitMilliseconds?: number) {
|
export function createSession(host: server.ServerHost, opts: Partial<server.SessionOptions> = {}) {
|
||||||
if (typingsInstaller === undefined) {
|
if (opts.typingsInstaller === undefined) {
|
||||||
typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
|
opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host);
|
||||||
}
|
}
|
||||||
const opts: server.SessionOptions = {
|
|
||||||
|
if (opts.eventHandler !== undefined) {
|
||||||
|
opts.canUseEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionOptions: server.SessionOptions = {
|
||||||
host,
|
host,
|
||||||
cancellationToken: cancellationToken || server.nullCancellationToken,
|
cancellationToken: server.nullCancellationToken,
|
||||||
useSingleInferredProject: false,
|
useSingleInferredProject: false,
|
||||||
typingsInstaller,
|
useInferredProjectPerProjectRoot: false,
|
||||||
|
typingsInstaller: undefined,
|
||||||
byteLength: Utils.byteLength,
|
byteLength: Utils.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
logger: nullLogger,
|
logger: nullLogger,
|
||||||
canUseEvents: projectServiceEventHandler !== undefined,
|
canUseEvents: false
|
||||||
eventHandler: projectServiceEventHandler,
|
|
||||||
throttleWaitMilliseconds
|
|
||||||
};
|
};
|
||||||
return new TestSession(opts);
|
|
||||||
|
return new TestSession({ ...sessionOptions, ...opts });
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateProjectServiceParameters {
|
interface CreateProjectServiceParameters {
|
||||||
|
@ -215,9 +220,16 @@ namespace ts.projectSystem {
|
||||||
|
|
||||||
export class TestProjectService extends server.ProjectService {
|
export class TestProjectService extends server.ProjectService {
|
||||||
constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean,
|
constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean,
|
||||||
typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler) {
|
typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler, opts: Partial<server.ProjectServiceOptions> = {}) {
|
||||||
super({
|
super({
|
||||||
host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler
|
host,
|
||||||
|
logger,
|
||||||
|
cancellationToken,
|
||||||
|
useSingleInferredProject,
|
||||||
|
useInferredProjectPerProjectRoot: false,
|
||||||
|
typingsInstaller,
|
||||||
|
eventHandler,
|
||||||
|
...opts
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,7 +643,7 @@ namespace ts.projectSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("tsserver-project-system", () => {
|
describe("tsserverProjectSystem", () => {
|
||||||
const commonFile1: FileOrFolder = {
|
const commonFile1: FileOrFolder = {
|
||||||
path: "/a/b/commonFile1.ts",
|
path: "/a/b/commonFile1.ts",
|
||||||
content: "let x = 1"
|
content: "let x = 1"
|
||||||
|
@ -2230,13 +2242,16 @@ namespace ts.projectSystem {
|
||||||
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
||||||
|
|
||||||
let lastEvent: server.ProjectLanguageServiceStateEvent;
|
let lastEvent: server.ProjectLanguageServiceStateEvent;
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
|
const session = createSession(host, {
|
||||||
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ContextEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
|
canUseEvents: true,
|
||||||
return;
|
eventHandler: e => {
|
||||||
|
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ContextEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
|
||||||
|
assert.equal(e.data.project.getProjectName(), config.path, "project name");
|
||||||
|
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
|
||||||
}
|
}
|
||||||
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
|
|
||||||
assert.equal(e.data.project.getProjectName(), config.path, "project name");
|
|
||||||
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
|
|
||||||
});
|
});
|
||||||
session.executeCommand(<protocol.OpenRequest>{
|
session.executeCommand(<protocol.OpenRequest>{
|
||||||
seq: 0,
|
seq: 0,
|
||||||
|
@ -2280,12 +2295,15 @@ namespace ts.projectSystem {
|
||||||
host.getFileSize = (filePath: string) =>
|
host.getFileSize = (filePath: string) =>
|
||||||
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
filePath === f2.path ? server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath);
|
||||||
let lastEvent: server.ProjectLanguageServiceStateEvent;
|
let lastEvent: server.ProjectLanguageServiceStateEvent;
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, e => {
|
const session = createSession(host, {
|
||||||
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
|
canUseEvents: true,
|
||||||
return;
|
eventHandler: e => {
|
||||||
|
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
|
||||||
|
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
|
||||||
}
|
}
|
||||||
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
|
|
||||||
lastEvent = <server.ProjectLanguageServiceStateEvent>e;
|
|
||||||
});
|
});
|
||||||
session.executeCommand(<protocol.OpenRequest>{
|
session.executeCommand(<protocol.OpenRequest>{
|
||||||
seq: 0,
|
seq: 0,
|
||||||
|
@ -3069,7 +3087,10 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
|
|
||||||
const host = createServerHost([file, configFile]);
|
const host = createServerHost([file, configFile]);
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, serverEventManager.handler);
|
const session = createSession(host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: serverEventManager.handler
|
||||||
|
});
|
||||||
openFilesForSession([file], session);
|
openFilesForSession([file], session);
|
||||||
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
||||||
|
|
||||||
|
@ -3096,7 +3117,10 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
|
|
||||||
const host = createServerHost([file, configFile]);
|
const host = createServerHost([file, configFile]);
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, serverEventManager.handler);
|
const session = createSession(host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: serverEventManager.handler
|
||||||
|
});
|
||||||
openFilesForSession([file], session);
|
openFilesForSession([file], session);
|
||||||
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
||||||
});
|
});
|
||||||
|
@ -3115,7 +3139,10 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
|
|
||||||
const host = createServerHost([file, configFile]);
|
const host = createServerHost([file, configFile]);
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, serverEventManager.handler);
|
const session = createSession(host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: serverEventManager.handler
|
||||||
|
});
|
||||||
openFilesForSession([file], session);
|
openFilesForSession([file], session);
|
||||||
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
serverEventManager.checkEventCountOfType("configFileDiag", 1);
|
||||||
|
|
||||||
|
@ -3504,6 +3531,93 @@ namespace ts.projectSystem {
|
||||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||||
checkProjectActualFiles(projectService.inferredProjects[0], [f.path]);
|
checkProjectActualFiles(projectService.inferredProjects[0], [f.path]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("inferred projects per project root", () => {
|
||||||
|
const file1 = { path: "/a/file1.ts", content: "let x = 1;", projectRootPath: "/a" };
|
||||||
|
const file2 = { path: "/a/file2.ts", content: "let y = 2;", projectRootPath: "/a" };
|
||||||
|
const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" };
|
||||||
|
const file4 = { path: "/c/file3.ts", content: "let z = 4;" };
|
||||||
|
const host = createServerHost([file1, file2, file3, file4]);
|
||||||
|
const session = createSession(host, {
|
||||||
|
useSingleInferredProject: true,
|
||||||
|
useInferredProjectPerProjectRoot: true
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||||
|
seq: 1,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.CompilerOptionsForInferredProjects,
|
||||||
|
arguments: {
|
||||||
|
options: {
|
||||||
|
allowJs: true,
|
||||||
|
target: ScriptTarget.ESNext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.SetCompilerOptionsForInferredProjectsRequest>{
|
||||||
|
seq: 2,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.CompilerOptionsForInferredProjects,
|
||||||
|
arguments: {
|
||||||
|
options: {
|
||||||
|
allowJs: true,
|
||||||
|
target: ScriptTarget.ES2015
|
||||||
|
},
|
||||||
|
projectRootPath: "/b"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.OpenRequest>{
|
||||||
|
seq: 3,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.Open,
|
||||||
|
arguments: {
|
||||||
|
file: file1.path,
|
||||||
|
fileContent: file1.content,
|
||||||
|
scriptKindName: "JS",
|
||||||
|
projectRootPath: file1.projectRootPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.OpenRequest>{
|
||||||
|
seq: 4,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.Open,
|
||||||
|
arguments: {
|
||||||
|
file: file2.path,
|
||||||
|
fileContent: file2.content,
|
||||||
|
scriptKindName: "JS",
|
||||||
|
projectRootPath: file2.projectRootPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.OpenRequest>{
|
||||||
|
seq: 5,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.Open,
|
||||||
|
arguments: {
|
||||||
|
file: file3.path,
|
||||||
|
fileContent: file3.content,
|
||||||
|
scriptKindName: "JS",
|
||||||
|
projectRootPath: file3.projectRootPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.executeCommand(<server.protocol.OpenRequest>{
|
||||||
|
seq: 6,
|
||||||
|
type: "request",
|
||||||
|
command: CommandNames.Open,
|
||||||
|
arguments: {
|
||||||
|
file: file4.path,
|
||||||
|
fileContent: file4.content,
|
||||||
|
scriptKindName: "JS"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectService = session.getProjectService();
|
||||||
|
checkNumberOfProjects(projectService, { inferredProjects: 3 });
|
||||||
|
checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]);
|
||||||
|
checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]);
|
||||||
|
checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]);
|
||||||
|
assert.equal(projectService.inferredProjects[0].getCompilerOptions().target, ScriptTarget.ESNext);
|
||||||
|
assert.equal(projectService.inferredProjects[1].getCompilerOptions().target, ScriptTarget.ESNext);
|
||||||
|
assert.equal(projectService.inferredProjects[2].getCompilerOptions().target, ScriptTarget.ES2015);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("No overwrite emit error", () => {
|
describe("No overwrite emit error", () => {
|
||||||
|
@ -3697,7 +3811,7 @@ namespace ts.projectSystem {
|
||||||
resetRequest: noop
|
resetRequest: noop
|
||||||
};
|
};
|
||||||
|
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, /*projectServiceEventHandler*/ undefined, cancellationToken);
|
const session = createSession(host, { cancellationToken });
|
||||||
|
|
||||||
expectedRequestId = session.getNextSeq();
|
expectedRequestId = session.getNextSeq();
|
||||||
session.executeCommandSeq(<server.protocol.OpenRequest>{
|
session.executeCommandSeq(<server.protocol.OpenRequest>{
|
||||||
|
@ -3737,7 +3851,11 @@ namespace ts.projectSystem {
|
||||||
|
|
||||||
const cancellationToken = new TestServerCancellationToken();
|
const cancellationToken = new TestServerCancellationToken();
|
||||||
const host = createServerHost([f1, config]);
|
const host = createServerHost([f1, config]);
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, () => { }, cancellationToken);
|
const session = createSession(host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: () => { },
|
||||||
|
cancellationToken
|
||||||
|
});
|
||||||
{
|
{
|
||||||
session.executeCommandSeq(<protocol.OpenRequest>{
|
session.executeCommandSeq(<protocol.OpenRequest>{
|
||||||
command: "open",
|
command: "open",
|
||||||
|
@ -3870,7 +3988,12 @@ namespace ts.projectSystem {
|
||||||
};
|
};
|
||||||
const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3);
|
const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3);
|
||||||
const host = createServerHost([f1, config]);
|
const host = createServerHost([f1, config]);
|
||||||
const session = createSession(host, /*typingsInstaller*/ undefined, () => { }, cancellationToken, /*throttleWaitMilliseconds*/ 0);
|
const session = createSession(host, {
|
||||||
|
canUseEvents: true,
|
||||||
|
eventHandler: () => { },
|
||||||
|
cancellationToken,
|
||||||
|
throttleWaitMilliseconds: 0
|
||||||
|
});
|
||||||
{
|
{
|
||||||
session.executeCommandSeq(<protocol.OpenRequest>{
|
session.executeCommandSeq(<protocol.OpenRequest>{
|
||||||
command: "open",
|
command: "open",
|
||||||
|
|
|
@ -323,6 +323,7 @@ namespace ts.server {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
cancellationToken: HostCancellationToken;
|
cancellationToken: HostCancellationToken;
|
||||||
useSingleInferredProject: boolean;
|
useSingleInferredProject: boolean;
|
||||||
|
useInferredProjectPerProjectRoot: boolean;
|
||||||
typingsInstaller: ITypingsInstaller;
|
typingsInstaller: ITypingsInstaller;
|
||||||
eventHandler?: ProjectServiceEventHandler;
|
eventHandler?: ProjectServiceEventHandler;
|
||||||
throttleWaitMilliseconds?: number;
|
throttleWaitMilliseconds?: number;
|
||||||
|
@ -364,7 +365,7 @@ namespace ts.server {
|
||||||
readonly openFiles: ScriptInfo[] = [];
|
readonly openFiles: ScriptInfo[] = [];
|
||||||
|
|
||||||
private compilerOptionsForInferredProjects: CompilerOptions;
|
private compilerOptionsForInferredProjects: CompilerOptions;
|
||||||
private compileOnSaveForInferredProjects: boolean;
|
private compilerOptionsForInferredProjectsPerProjectRoot = createMap<CompilerOptions>();
|
||||||
private readonly projectToSizeMap: Map<number> = createMap<number>();
|
private readonly projectToSizeMap: Map<number> = createMap<number>();
|
||||||
private readonly directoryWatchers: DirectoryWatchers;
|
private readonly directoryWatchers: DirectoryWatchers;
|
||||||
private readonly throttledOperations: ThrottledOperations;
|
private readonly throttledOperations: ThrottledOperations;
|
||||||
|
@ -382,6 +383,7 @@ namespace ts.server {
|
||||||
public readonly logger: Logger;
|
public readonly logger: Logger;
|
||||||
public readonly cancellationToken: HostCancellationToken;
|
public readonly cancellationToken: HostCancellationToken;
|
||||||
public readonly useSingleInferredProject: boolean;
|
public readonly useSingleInferredProject: boolean;
|
||||||
|
public readonly useInferredProjectPerProjectRoot: boolean;
|
||||||
public readonly typingsInstaller: ITypingsInstaller;
|
public readonly typingsInstaller: ITypingsInstaller;
|
||||||
public readonly throttleWaitMilliseconds?: number;
|
public readonly throttleWaitMilliseconds?: number;
|
||||||
private readonly eventHandler?: ProjectServiceEventHandler;
|
private readonly eventHandler?: ProjectServiceEventHandler;
|
||||||
|
@ -398,6 +400,7 @@ namespace ts.server {
|
||||||
this.logger = opts.logger;
|
this.logger = opts.logger;
|
||||||
this.cancellationToken = opts.cancellationToken;
|
this.cancellationToken = opts.cancellationToken;
|
||||||
this.useSingleInferredProject = opts.useSingleInferredProject;
|
this.useSingleInferredProject = opts.useSingleInferredProject;
|
||||||
|
this.useInferredProjectPerProjectRoot = opts.useInferredProjectPerProjectRoot;
|
||||||
this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller;
|
this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller;
|
||||||
this.throttleWaitMilliseconds = opts.throttleWaitMilliseconds;
|
this.throttleWaitMilliseconds = opts.throttleWaitMilliseconds;
|
||||||
this.eventHandler = opts.eventHandler;
|
this.eventHandler = opts.eventHandler;
|
||||||
|
@ -464,17 +467,42 @@ namespace ts.server {
|
||||||
project.updateGraph();
|
project.updateGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void {
|
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void {
|
||||||
this.compilerOptionsForInferredProjects = convertCompilerOptions(projectCompilerOptions);
|
Debug.assert(projectRootPath === undefined || this.useInferredProjectPerProjectRoot, "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled");
|
||||||
|
|
||||||
|
const compilerOptions = convertCompilerOptions(projectCompilerOptions);
|
||||||
|
|
||||||
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
|
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
|
||||||
// previously we did not expose a way for user to change these settings and this option was enabled by default
|
// previously we did not expose a way for user to change these settings and this option was enabled by default
|
||||||
this.compilerOptionsForInferredProjects.allowNonTsExtensions = true;
|
compilerOptions.allowNonTsExtensions = true;
|
||||||
this.compileOnSaveForInferredProjects = projectCompilerOptions.compileOnSave;
|
|
||||||
for (const proj of this.inferredProjects) {
|
if (projectRootPath) {
|
||||||
proj.setCompilerOptions(this.compilerOptionsForInferredProjects);
|
this.compilerOptionsForInferredProjectsPerProjectRoot.set(projectRootPath, compilerOptions);
|
||||||
proj.compileOnSaveEnabled = projectCompilerOptions.compileOnSave;
|
|
||||||
}
|
}
|
||||||
this.updateProjectGraphs(this.inferredProjects);
|
else {
|
||||||
|
this.compilerOptionsForInferredProjects = compilerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedProjects: Project[] = [];
|
||||||
|
for (const project of this.inferredProjects) {
|
||||||
|
// Only update compiler options in the following cases:
|
||||||
|
// - Inferred projects without a projectRootPath, if the new options do not apply to
|
||||||
|
// a workspace root
|
||||||
|
// - Inferred projects with a projectRootPath, if the new options do not apply to a
|
||||||
|
// workspace root and there is no more specific set of options for that project's
|
||||||
|
// root path
|
||||||
|
// - Inferred projects with a projectRootPath, if the new options apply to that
|
||||||
|
// project root path.
|
||||||
|
if (projectRootPath ?
|
||||||
|
project.projectRootPath === projectRootPath :
|
||||||
|
!project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) {
|
||||||
|
project.setCompilerOptions(compilerOptions);
|
||||||
|
project.compileOnSaveEnabled = compilerOptions.compileOnSave;
|
||||||
|
updatedProjects.push(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateProjectGraphs(updatedProjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopWatchingDirectory(directory: string) {
|
stopWatchingDirectory(directory: string) {
|
||||||
|
@ -715,7 +743,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean): void {
|
private assignScriptInfoToInferredProjectIfNecessary(info: ScriptInfo, addToListOfOpenFiles: boolean, projectRootPath?: string): void {
|
||||||
const externalProject = this.findContainingExternalProject(info.fileName);
|
const externalProject = this.findContainingExternalProject(info.fileName);
|
||||||
if (externalProject) {
|
if (externalProject) {
|
||||||
// file is already included in some external project - do nothing
|
// file is already included in some external project - do nothing
|
||||||
|
@ -743,30 +771,30 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.containingProjects.length === 0) {
|
if (info.containingProjects.length === 0) {
|
||||||
// create new inferred project p with the newly opened file as root
|
// get (or create) an inferred project using the newly opened file as a root.
|
||||||
// or add root to existing inferred project if 'useOneInferredProject' is true
|
const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info, projectRootPath);
|
||||||
const inferredProject = this.createInferredProjectWithRootFileIfNecessary(info);
|
if (!this.useSingleInferredProject && !inferredProject.projectRootPath) {
|
||||||
if (!this.useSingleInferredProject) {
|
// if useSingleInferredProject is false and the inferred project is not associated
|
||||||
// if useOneInferredProject is not set then try to fixup ownership of open files
|
// with a project root, then try to repair the ownership of open files.
|
||||||
// check 'defaultProject !== inferredProject' is necessary to handle cases
|
|
||||||
// when creation inferred project for some file has added other open files into this project (i.e. as referenced files)
|
|
||||||
// we definitely don't want to delete the project that was just created
|
|
||||||
for (const f of this.openFiles) {
|
for (const f of this.openFiles) {
|
||||||
if (f.containingProjects.length === 0 || !inferredProject.containsScriptInfo(f)) {
|
if (f.containingProjects.length === 0 || !inferredProject.containsScriptInfo(f)) {
|
||||||
// this is orphaned file that we have not processed yet - skip it
|
// this is orphaned file that we have not processed yet - skip it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const fContainingProject of f.containingProjects) {
|
for (const containingProject of f.containingProjects) {
|
||||||
if (fContainingProject.projectKind === ProjectKind.Inferred &&
|
// We verify 'containingProject !== inferredProject' to handle cases
|
||||||
fContainingProject.isRoot(f) &&
|
// where the inferred project for some file has added other open files
|
||||||
fContainingProject !== inferredProject) {
|
// into this project (i.e. as referenced files) as we don't want to
|
||||||
|
// delete the project that was just created
|
||||||
|
if (containingProject.projectKind === ProjectKind.Inferred &&
|
||||||
|
containingProject !== inferredProject &&
|
||||||
|
containingProject.isRoot(f)) {
|
||||||
// open file used to be root in inferred project,
|
// open file used to be root in inferred project,
|
||||||
// this inferred project is different from the one we've just created for current file
|
// this inferred project is different from the one we've just created for current file
|
||||||
// and new inferred project references this open file.
|
// and new inferred project references this open file.
|
||||||
// We should delete old inferred project and attach open file to the new one
|
// We should delete old inferred project and attach open file to the new one
|
||||||
this.removeProject(fContainingProject);
|
this.removeProject(containingProject);
|
||||||
f.attachToProject(inferredProject);
|
f.attachToProject(inferredProject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1286,11 +1314,74 @@ namespace ts.server {
|
||||||
return configFileErrors;
|
return configFileErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo) {
|
private getOrCreateInferredProjectForProjectRootPathIfEnabled(root: ScriptInfo, projectRootPath: string | undefined): InferredProject | undefined {
|
||||||
const useExistingProject = this.useSingleInferredProject && this.inferredProjects.length;
|
if (!this.useInferredProjectPerProjectRoot) {
|
||||||
const project = useExistingProject
|
return undefined;
|
||||||
? this.inferredProjects[0]
|
}
|
||||||
: new InferredProject(this, this.documentRegistry, this.compilerOptionsForInferredProjects);
|
|
||||||
|
if (projectRootPath) {
|
||||||
|
// if we have an explicit project root path, find (or create) the matching inferred project.
|
||||||
|
for (const project of this.inferredProjects) {
|
||||||
|
if (project.projectRootPath === projectRootPath) {
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.createInferredProject(/*isSingleInferredProject*/ false, projectRootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't have an explicit root path, so we should try to find an inferred project
|
||||||
|
// that more closely contains the file.
|
||||||
|
let bestMatch: InferredProject;
|
||||||
|
for (const project of this.inferredProjects) {
|
||||||
|
// ignore single inferred projects (handled elsewhere)
|
||||||
|
if (!project.projectRootPath) continue;
|
||||||
|
// ignore inferred projects that don't contain the root's path
|
||||||
|
if (!containsPath(project.projectRootPath, root.path, this.host.getCurrentDirectory(), !this.host.useCaseSensitiveFileNames)) continue;
|
||||||
|
// ignore inferred projects that are higher up in the project root.
|
||||||
|
// TODO(rbuckton): Should we add the file as a root to these as well?
|
||||||
|
if (bestMatch && bestMatch.projectRootPath.length > project.projectRootPath.length) continue;
|
||||||
|
bestMatch = project;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOrCreateSingleInferredProjectIfEnabled(): InferredProject | undefined {
|
||||||
|
if (!this.useSingleInferredProject) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `useInferredProjectPerProjectRoot` is not enabled, then there will only be one
|
||||||
|
// inferred project for all files. If `useInferredProjectPerProjectRoot` is enabled
|
||||||
|
// then we want to put all files that are not opened with a `projectRootPath` into
|
||||||
|
// the same inferred project.
|
||||||
|
//
|
||||||
|
// To avoid the cost of searching through the array and to optimize for the case where
|
||||||
|
// `useInferredProjectPerProjectRoot` is not enabled, we will always put the inferred
|
||||||
|
// project for non-rooted files at the front of the array.
|
||||||
|
if (this.inferredProjects.length > 0 && this.inferredProjects[0].projectRootPath === undefined) {
|
||||||
|
return this.inferredProjects[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createInferredProject(/*isSingleInferredProject*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createInferredProject(isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject {
|
||||||
|
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
|
||||||
|
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath);
|
||||||
|
if (isSingleInferredProject) {
|
||||||
|
this.inferredProjects.unshift(project);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.inferredProjects.push(project);
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
}
|
||||||
|
|
||||||
|
createInferredProjectWithRootFileIfNecessary(root: ScriptInfo, projectRootPath?: string) {
|
||||||
|
const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(root, projectRootPath) ||
|
||||||
|
this.getOrCreateSingleInferredProjectIfEnabled() ||
|
||||||
|
this.createInferredProject();
|
||||||
|
|
||||||
project.addRoot(root);
|
project.addRoot(root);
|
||||||
|
|
||||||
|
@ -1301,9 +1392,6 @@ namespace ts.server {
|
||||||
|
|
||||||
project.updateGraph();
|
project.updateGraph();
|
||||||
|
|
||||||
if (!useExistingProject) {
|
|
||||||
this.inferredProjects.push(project);
|
|
||||||
}
|
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1477,7 +1565,7 @@ namespace ts.server {
|
||||||
|
|
||||||
// at this point if file is the part of some configured/external project then this project should be created
|
// at this point if file is the part of some configured/external project then this project should be created
|
||||||
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
|
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
|
||||||
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true);
|
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true, projectRootPath);
|
||||||
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
|
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
|
||||||
// when some file/s were closed which resulted in project removal.
|
// when some file/s were closed which resulted in project removal.
|
||||||
// It was then postponed to cleanup these script infos so that they can be reused if
|
// It was then postponed to cleanup these script infos so that they can be reused if
|
||||||
|
|
|
@ -837,6 +837,7 @@ namespace ts.server {
|
||||||
* the file and its imports/references are put into an InferredProject.
|
* the file and its imports/references are put into an InferredProject.
|
||||||
*/
|
*/
|
||||||
export class InferredProject extends Project {
|
export class InferredProject extends Project {
|
||||||
|
public readonly projectRootPath: string | undefined;
|
||||||
|
|
||||||
private static readonly newName = (() => {
|
private static readonly newName = (() => {
|
||||||
let nextId = 1;
|
let nextId = 1;
|
||||||
|
@ -876,7 +877,7 @@ namespace ts.server {
|
||||||
// Used to keep track of what directories are watched for this project
|
// Used to keep track of what directories are watched for this project
|
||||||
directoriesWatchedForTsconfig: string[] = [];
|
directoriesWatchedForTsconfig: string[] = [];
|
||||||
|
|
||||||
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
|
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, projectRootPath?: string) {
|
||||||
super(InferredProject.newName(),
|
super(InferredProject.newName(),
|
||||||
ProjectKind.Inferred,
|
ProjectKind.Inferred,
|
||||||
projectService,
|
projectService,
|
||||||
|
@ -885,6 +886,7 @@ namespace ts.server {
|
||||||
/*languageServiceEnabled*/ true,
|
/*languageServiceEnabled*/ true,
|
||||||
compilerOptions,
|
compilerOptions,
|
||||||
/*compileOnSaveEnabled*/ false);
|
/*compileOnSaveEnabled*/ false);
|
||||||
|
this.projectRootPath = projectRootPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoot(info: ScriptInfo) {
|
addRoot(info: ScriptInfo) {
|
||||||
|
|
|
@ -1304,6 +1304,13 @@ namespace ts.server.protocol {
|
||||||
* Compiler options to be used with inferred projects.
|
* Compiler options to be used with inferred projects.
|
||||||
*/
|
*/
|
||||||
options: ExternalProjectCompilerOptions;
|
options: ExternalProjectCompilerOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the project root path used to scope compiler options.
|
||||||
|
* It is an error to provide this property if the server has not been started with
|
||||||
|
* `useInferredProjectPerProjectRoot` enabled.
|
||||||
|
*/
|
||||||
|
projectRootPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace ts.server {
|
||||||
canUseEvents: boolean;
|
canUseEvents: boolean;
|
||||||
installerEventPort: number;
|
installerEventPort: number;
|
||||||
useSingleInferredProject: boolean;
|
useSingleInferredProject: boolean;
|
||||||
|
useInferredProjectPerProjectRoot: boolean;
|
||||||
disableAutomaticTypingAcquisition: boolean;
|
disableAutomaticTypingAcquisition: boolean;
|
||||||
globalTypingsCacheLocation: string;
|
globalTypingsCacheLocation: string;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
@ -414,6 +415,7 @@ namespace ts.server {
|
||||||
host,
|
host,
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
useSingleInferredProject,
|
useSingleInferredProject,
|
||||||
|
useInferredProjectPerProjectRoot,
|
||||||
typingsInstaller: typingsInstaller || nullTypingsInstaller,
|
typingsInstaller: typingsInstaller || nullTypingsInstaller,
|
||||||
byteLength: Buffer.byteLength,
|
byteLength: Buffer.byteLength,
|
||||||
hrtime: process.hrtime,
|
hrtime: process.hrtime,
|
||||||
|
@ -779,6 +781,7 @@ namespace ts.server {
|
||||||
const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads");
|
const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads");
|
||||||
|
|
||||||
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
|
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
|
||||||
|
const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot");
|
||||||
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
|
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
|
||||||
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
|
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
|
||||||
|
|
||||||
|
@ -788,6 +791,7 @@ namespace ts.server {
|
||||||
installerEventPort: eventPort,
|
installerEventPort: eventPort,
|
||||||
canUseEvents: eventPort === undefined,
|
canUseEvents: eventPort === undefined,
|
||||||
useSingleInferredProject,
|
useSingleInferredProject,
|
||||||
|
useInferredProjectPerProjectRoot,
|
||||||
disableAutomaticTypingAcquisition,
|
disableAutomaticTypingAcquisition,
|
||||||
globalTypingsCacheLocation: getGlobalTypingsCacheLocation(),
|
globalTypingsCacheLocation: getGlobalTypingsCacheLocation(),
|
||||||
typingSafeListLocation,
|
typingSafeListLocation,
|
||||||
|
|
|
@ -247,6 +247,7 @@ namespace ts.server {
|
||||||
host: ServerHost;
|
host: ServerHost;
|
||||||
cancellationToken: ServerCancellationToken;
|
cancellationToken: ServerCancellationToken;
|
||||||
useSingleInferredProject: boolean;
|
useSingleInferredProject: boolean;
|
||||||
|
useInferredProjectPerProjectRoot: boolean;
|
||||||
typingsInstaller: ITypingsInstaller;
|
typingsInstaller: ITypingsInstaller;
|
||||||
byteLength: (buf: string, encoding?: string) => number;
|
byteLength: (buf: string, encoding?: string) => number;
|
||||||
hrtime: (start?: number[]) => number[];
|
hrtime: (start?: number[]) => number[];
|
||||||
|
@ -307,6 +308,7 @@ namespace ts.server {
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
cancellationToken: this.cancellationToken,
|
cancellationToken: this.cancellationToken,
|
||||||
useSingleInferredProject: opts.useSingleInferredProject,
|
useSingleInferredProject: opts.useSingleInferredProject,
|
||||||
|
useInferredProjectPerProjectRoot: opts.useInferredProjectPerProjectRoot,
|
||||||
typingsInstaller: this.typingsInstaller,
|
typingsInstaller: this.typingsInstaller,
|
||||||
throttleWaitMilliseconds,
|
throttleWaitMilliseconds,
|
||||||
eventHandler: this.eventHandler,
|
eventHandler: this.eventHandler,
|
||||||
|
@ -743,7 +745,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
|
private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
|
||||||
this.projectService.setCompilerOptionsForInferredProjects(args.options);
|
this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
|
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
|
||||||
|
|
Loading…
Reference in a new issue