diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index c6477240161..3bf6f1cde9a 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -818,13 +818,17 @@ namespace Harness.LanguageService { // This host is just a proxy for the clientHost, it uses the client // host to answer server queries about files on disk const serverHost = new SessionServerHost(clientHost); - const server = new ts.server.Session(serverHost, - ts.server.nullCancellationToken, - /*useOneInferredProject*/ false, - /*typingsInstaller*/ undefined, - Utils.byteLength, - process.hrtime, serverHost, - /*canUseEvents*/ true); + const opts: ts.server.SessionOptions = { + host: serverHost, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller: undefined, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: serverHost, + canUseEvents: true + }; + const server = new ts.server.Session(opts); // Fake the connection between the client and the server serverHost.writeMessage = client.onMessage.bind(client); diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 5389c8f37f8..5ce93fa8231 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -64,8 +64,16 @@ namespace ts { getLogFileName: (): string => undefined }; - const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined); - const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /*openedByClient*/ true, /*containingProject*/ undefined); + const svcOpts: server.ProjectServiceOptions = { + host: serverHost, + logger, + cancellationToken: { isCancellationRequested: () => false }, + useSingleInferredProject: false, + typingsInstaller: undefined + }; + const projectService = new server.ProjectService(svcOpts); + const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true, /*containingProject*/ undefined); + const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD } ); return { diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 114ee77fef1..506559bd3d9 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -31,6 +31,20 @@ namespace ts.projectSystem { } } + function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller): server.Session { + const opts: server.SessionOptions = { + host, + cancellationToken: nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller: typingsInstaller || server.nullTypingsInstaller, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: nullLogger, + canUseEvents: false + }; + return new server.Session(opts); + } + describe("for configured projects", () => { let moduleFile1: FileOrFolder; let file1Consumer1: FileOrFolder; @@ -113,7 +127,7 @@ namespace ts.projectSystem { it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1, file1Consumer1], session); @@ -138,7 +152,7 @@ namespace ts.projectSystem { it("should be up-to-date with the reference map changes", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1, file1Consumer1], session); @@ -185,7 +199,7 @@ namespace ts.projectSystem { it("should be up-to-date with changes made in non-open files", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); @@ -203,7 +217,7 @@ namespace ts.projectSystem { it("should be up-to-date with deleted files", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); @@ -218,7 +232,7 @@ namespace ts.projectSystem { it("should be up-to-date with newly created files", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]); @@ -255,7 +269,7 @@ namespace ts.projectSystem { const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1, file1Consumer1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]); @@ -272,7 +286,7 @@ namespace ts.projectSystem { it("should return all files if a global file changed shape", () => { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([globalFile3], session); const changeGlobalFile3ShapeRequest = makeSessionRequest(CommandNames.Change, { @@ -298,7 +312,7 @@ namespace ts.projectSystem { const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []); }); @@ -316,7 +330,7 @@ namespace ts.projectSystem { const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { @@ -345,7 +359,7 @@ namespace ts.projectSystem { const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1], session); const file1ChangeShapeRequest = makeSessionRequest(CommandNames.Change, { @@ -367,7 +381,7 @@ namespace ts.projectSystem { }; const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([moduleFile1, file1Consumer1], session); sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]); @@ -400,7 +414,7 @@ namespace ts.projectSystem { }; const host = createServerHost([file1, file2, configFile]); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([file1, file2], session); const file1AffectedListRequest = makeSessionRequest(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path }); @@ -513,7 +527,7 @@ namespace ts.projectSystem { }; const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); const typingsInstaller = createTestTypingsInstaller(host); - const session = new server.Session(host, nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ false); + const session = createSession(host, typingsInstaller); openFilesForSession([file1, file2], session); const compileFileRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path }); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 9a38de7da10..a505c3ee7be 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -50,8 +50,22 @@ namespace ts.server { let session: TestSession; let lastSent: protocol.Message; + function createSession(): TestSession { + const opts: server.SessionOptions = { + host: mockHost, + cancellationToken: nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller: undefined, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: mockLogger, + canUseEvents: true + }; + return new TestSession(opts); + } + beforeEach(() => { - session = new TestSession(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true); + session = createSession(); session.send = (msg: protocol.Message) => { lastSent = msg; }; @@ -318,7 +332,16 @@ namespace ts.server { lastSent: protocol.Message; customHandler = "testhandler"; constructor() { - super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true); + super({ + host: mockHost, + cancellationToken: nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller: undefined, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: mockLogger, + canUseEvents: true + }); this.addProtocolHandler(this.customHandler, () => { return { response: undefined, responseRequired: true }; }); @@ -376,7 +399,16 @@ namespace ts.server { class InProcSession extends Session { private queue: protocol.Request[] = []; constructor(private client: InProcClient) { - super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, /*typingsInstaller*/ undefined, Utils.byteLength, process.hrtime, mockLogger, /*canUseEvents*/ true); + super({ + host: mockHost, + cancellationToken: nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller: undefined, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: mockLogger, + canUseEvents: true + }); this.addProtocolHandler("echo", (req: protocol.Request) => ({ response: req.arguments, responseRequired: true diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 8364420709c..81931a2de41 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -190,7 +190,19 @@ namespace ts.projectSystem { if (typingsInstaller === undefined) { typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host); } - return new TestSession(host, cancellationToken || server.nullCancellationToken, /*useSingleInferredProject*/ false, typingsInstaller, Utils.byteLength, process.hrtime, nullLogger, /*canUseEvents*/ projectServiceEventHandler !== undefined, projectServiceEventHandler, throttleWaitMilliseconds); + const opts: server.SessionOptions = { + host, + cancellationToken: cancellationToken || server.nullCancellationToken, + useSingleInferredProject: false, + typingsInstaller, + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: nullLogger, + canUseEvents: projectServiceEventHandler !== undefined, + eventHandler: projectServiceEventHandler, + throttleWaitMilliseconds + }; + return new TestSession(opts); } export interface CreateProjectServiceParameters { @@ -205,7 +217,9 @@ namespace ts.projectSystem { export class TestProjectService extends server.ProjectService { constructor(host: server.ServerHost, logger: server.Logger, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, typingsInstaller: server.ITypingsInstaller, eventHandler: server.ProjectServiceEventHandler) { - super(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler); + super({ + host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, eventHandler + }); } checkNumberOfProjects(count: { inferredProjects?: number, configuredProjects?: number, externalProjects?: number }) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 28d18ad5577..f4f1e66e1ff 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -273,6 +273,18 @@ namespace ts.server { } } + export interface ProjectServiceOptions { + host: ServerHost; + logger: Logger; + cancellationToken: HostCancellationToken; + useSingleInferredProject: boolean; + typingsInstaller: ITypingsInstaller; + eventHandler?: ProjectServiceEventHandler; + throttleWaitMilliseconds?: number; + globalPlugins?: string[]; + pluginProbeLocations?: string[]; + } + export class ProjectService { public readonly typingsCache: TypingsCache; @@ -320,19 +332,33 @@ namespace ts.server { public lastDeletedFile: ScriptInfo; - constructor(public readonly host: ServerHost, - public readonly logger: Logger, - public readonly cancellationToken: HostCancellationToken, - public readonly useSingleInferredProject: boolean, - readonly typingsInstaller: ITypingsInstaller = nullTypingsInstaller, - private readonly eventHandler?: ProjectServiceEventHandler, - public readonly throttleWaitMilliseconds?: number) { + public readonly host: ServerHost; + public readonly logger: Logger; + public readonly cancellationToken: HostCancellationToken; + public readonly useSingleInferredProject: boolean; + public readonly typingsInstaller: ITypingsInstaller; + public readonly throttleWaitMilliseconds?: number; + private readonly eventHandler?: ProjectServiceEventHandler; - Debug.assert(!!host.createHash, "'ServerHost.createHash' is required for ProjectService"); + public readonly globalPlugins: ReadonlyArray; + public readonly pluginProbeLocations: ReadonlyArray; - this.toCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + constructor(opts: ProjectServiceOptions) { + this.host = opts.host; + this.logger = opts.logger; + this.cancellationToken = opts.cancellationToken; + this.useSingleInferredProject = opts.useSingleInferredProject; + this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller; + this.throttleWaitMilliseconds = opts.throttleWaitMilliseconds; + this.eventHandler = opts.eventHandler; + this.globalPlugins = opts.globalPlugins || emptyArray; + this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray; + + Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); + + this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.directoryWatchers = new DirectoryWatchers(this); - this.throttledOperations = new ThrottledOperations(host); + this.throttledOperations = new ThrottledOperations(this.host); this.typingsInstaller.attach(this); @@ -344,7 +370,7 @@ namespace ts.server { extraFileExtensions: [] }; - this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory()); + this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.host.getCurrentDirectory()); } /* @internal */ diff --git a/src/server/project.ts b/src/server/project.ts index e57e605d65b..b79df827b6b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -843,7 +843,7 @@ namespace ts.server { /** Used for configured projects which may have multiple open roots */ openRefCount = 0; - constructor(private configFileName: NormalizedPath, + constructor(configFileName: NormalizedPath, projectService: ProjectService, documentRegistry: ts.DocumentRegistry, hasExplicitListOfFiles: boolean, @@ -863,9 +863,6 @@ namespace ts.server { enablePlugins() { const host = this.projectService.host; const options = this.getCompilerOptions(); - const log = (message: string) => { - this.projectService.logger.info(message); - }; if (!(options.plugins && options.plugins.length)) { this.projectService.logger.info("No plugins exist"); @@ -878,13 +875,34 @@ namespace ts.server { return; } + // Search our peer node_modules, then any globally-specified probe paths + // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/ + const searchPaths = [combinePaths(host.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations]; + + // Enable tsconfig-specified plugins for (const pluginConfigEntry of options.plugins) { - const searchPath = getDirectoryPath(this.configFileName); - const resolvedModule = Project.resolveModule(pluginConfigEntry.name, searchPath, host, log); + this.enablePlugin(pluginConfigEntry, searchPaths); + } + // Enable global plugins with synthetic configuration entries + for (const globalPluginName of this.projectService.globalPlugins) { + // Provide global: true so plugins can detect why they can't find their config + this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths); + } + } + + private enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) { + const log = (message: string) => { + this.projectService.logger.info(message); + }; + + for (const searchPath of searchPaths) { + const resolvedModule = Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log); if (resolvedModule) { this.enableProxy(resolvedModule, pluginConfigEntry); + return; } } + this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(",")}`); } private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) { diff --git a/src/server/server.ts b/src/server/server.ts index 704193210e3..433c7e16ab1 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -3,6 +3,20 @@ /// namespace ts.server { + interface IOSessionOptions { + host: ServerHost; + cancellationToken: ServerCancellationToken; + canUseEvents: boolean; + installerEventPort: number; + useSingleInferredProject: boolean; + disableAutomaticTypingAcquisition: boolean; + globalTypingsCacheLocation: string; + logger: Logger; + typingSafeListLocation: string; + telemetryEnabled: boolean; + globalPlugins: string[]; + pluginProbeLocations: string[]; + } const net: { connect(options: { port: number }, onConnect?: () => void): NodeSocket @@ -373,34 +387,25 @@ namespace ts.server { } class IOSession extends Session { - constructor( - host: ServerHost, - cancellationToken: ServerCancellationToken, - installerEventPort: number, - canUseEvents: boolean, - useSingleInferredProject: boolean, - disableAutomaticTypingAcquisition: boolean, - globalTypingsCacheLocation: string, - typingSafeListLocation: string, - telemetryEnabled: boolean, - logger: server.Logger) { - const typingsInstaller = disableAutomaticTypingAcquisition - ? undefined - : new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, host.newLine); + constructor(options: IOSessionOptions) { + const { host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, canUseEvents } = options; + const typingsInstaller = disableAutomaticTypingAcquisition + ? undefined + : new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, typingSafeListLocation, host.newLine); - super( - host, - cancellationToken, - useSingleInferredProject, - typingsInstaller || nullTypingsInstaller, - Buffer.byteLength, - process.hrtime, - logger, - canUseEvents); + super({ + host, + cancellationToken, + useSingleInferredProject, + typingsInstaller: typingsInstaller || nullTypingsInstaller, + byteLength: Buffer.byteLength, + hrtime: process.hrtime, + logger, + canUseEvents}); - if (telemetryEnabled && typingsInstaller) { - typingsInstaller.setTelemetrySender(this); - } + if (telemetryEnabled && typingsInstaller) { + typingsInstaller.setTelemetrySender(this); + } } exit() { @@ -735,21 +740,29 @@ namespace ts.server { const typingSafeListLocation = findArgument("--typingSafeListLocation"); + const globalPlugins = (findArgument("--globalPlugins") || "").split(","); + const pluginProbeLocations = (findArgument("--pluginProbeLocations") || "").split(","); + const useSingleInferredProject = hasArgument("--useSingleInferredProject"); const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); - const ioSession = new IOSession( - sys, + const options: IOSessionOptions = { + host: sys, cancellationToken, - eventPort, - /*canUseEvents*/ eventPort === undefined, + installerEventPort: eventPort, + canUseEvents: eventPort === undefined, useSingleInferredProject, disableAutomaticTypingAcquisition, - getGlobalTypingsCacheLocation(), + globalTypingsCacheLocation: getGlobalTypingsCacheLocation(), typingSafeListLocation, telemetryEnabled, - logger); + logger, + globalPlugins, + pluginProbeLocations + }; + + const ioSession = new IOSession(options); process.on("uncaughtException", function (err: Error) { ioSession.logError(err, "unknown"); }); diff --git a/src/server/session.ts b/src/server/session.ts index 26dbceece45..d4dc371b16b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -319,6 +319,22 @@ namespace ts.server { } } + export interface SessionOptions { + host: ServerHost; + cancellationToken: ServerCancellationToken; + useSingleInferredProject: boolean; + typingsInstaller: ITypingsInstaller; + byteLength: (buf: string, encoding?: string) => number; + hrtime: (start?: number[]) => number[]; + logger: Logger; + canUseEvents: boolean; + eventHandler?: ProjectServiceEventHandler; + throttleWaitMilliseconds?: number; + + globalPlugins?: string[]; + pluginProbeLocations?: string[]; + } + export class Session implements EventSender { private readonly gcTimer: GcTimer; protected projectService: ProjectService; @@ -327,22 +343,29 @@ namespace ts.server { private currentRequestId: number; private errorCheck: MultistepOperation; - private eventHander: ProjectServiceEventHandler; + private eventHandler: ProjectServiceEventHandler; - constructor( - private host: ServerHost, - private readonly cancellationToken: ServerCancellationToken, - useSingleInferredProject: boolean, - protected readonly typingsInstaller: ITypingsInstaller, - private byteLength: (buf: string, encoding?: string) => number, - private hrtime: (start?: number[]) => number[], - protected logger: Logger, - protected readonly canUseEvents: boolean, - eventHandler?: ProjectServiceEventHandler, - private readonly throttleWaitMilliseconds?: number) { + private host: ServerHost; + private readonly cancellationToken: ServerCancellationToken; + protected readonly typingsInstaller: ITypingsInstaller; + private byteLength: (buf: string, encoding?: string) => number; + private hrtime: (start?: number[]) => number[]; + protected logger: Logger; + private canUseEvents: boolean; - this.eventHander = canUseEvents - ? eventHandler || (event => this.defaultEventHandler(event)) + constructor(opts: SessionOptions) { + this.host = opts.host; + this.cancellationToken = opts.cancellationToken; + this.typingsInstaller = opts.typingsInstaller; + this.byteLength = opts.byteLength; + this.hrtime = opts.hrtime; + this.logger = opts.logger; + this.canUseEvents = opts.canUseEvents; + + const { throttleWaitMilliseconds } = opts; + + this.eventHandler = this.canUseEvents + ? opts.eventHandler || (event => this.defaultEventHandler(event)) : undefined; const multistepOperationHost: MultistepOperationHost = { @@ -351,11 +374,22 @@ namespace ts.server { getServerHost: () => this.host, logError: (err, cmd) => this.logError(err, cmd), sendRequestCompletedEvent: requestId => this.sendRequestCompletedEvent(requestId), - isCancellationRequested: () => cancellationToken.isCancellationRequested() + isCancellationRequested: () => this.cancellationToken.isCancellationRequested() }; this.errorCheck = new MultistepOperation(multistepOperationHost); - this.projectService = new ProjectService(host, logger, cancellationToken, useSingleInferredProject, typingsInstaller, this.eventHander, this.throttleWaitMilliseconds); - this.gcTimer = new GcTimer(host, /*delay*/ 7000, logger); + const settings: ProjectServiceOptions = { + host: this.host, + logger: this.logger, + cancellationToken: this.cancellationToken, + useSingleInferredProject: opts.useSingleInferredProject, + typingsInstaller: this.typingsInstaller, + throttleWaitMilliseconds, + eventHandler: this.eventHandler, + globalPlugins: opts.globalPlugins, + pluginProbeLocations: opts.pluginProbeLocations + }; + this.projectService = new ProjectService(settings); + this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger); } private sendRequestCompletedEvent(requestId: number): void { @@ -947,8 +981,8 @@ namespace ts.server { */ private openClientFile(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind) { const { configFileName, configFileErrors } = this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind); - if (this.eventHander) { - this.eventHander({ + if (this.eventHandler) { + this.eventHandler({ eventName: "configFileDiag", data: { triggerFile: fileName, configFileName, diagnostics: configFileErrors || [] } });