From cee980cf1a13d1734026bf4b4963215bd64ff24c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 26 Jun 2020 17:44:05 -0700 Subject: [PATCH 1/5] Make tsserver.js a library VS Code (and probably others) currently consumes both tsserver.js and typescript.js, even though they have essentially the same content. This change makes it possible to use tsserver.js for both purposes, by suppressing the server startup code (including requires) and adding a declaration file (stripped of internals). --- src/tsserver/server.ts | 1686 ++++++++++++++++++------------------ src/tsserver/tsconfig.json | 4 +- 2 files changed, 846 insertions(+), 844 deletions(-) diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 43961eb0ba597..1690fcd2ac960 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -1,992 +1,994 @@ namespace ts.server { - const childProcess: { - fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; - execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike }): string | Buffer; - } = require("child_process"); - - const os: { - homedir?(): string; - tmpdir(): string; - platform(): string; - } = require("os"); - - interface NodeSocket { - write(data: string, encoding: string): boolean; - } - - const net: { - connect(options: { port: number }, onConnect?: () => void): NodeSocket - } = require("net"); - - function getGlobalTypingsCacheLocation() { - switch (process.platform) { - case "win32": { - const basePath = process.env.LOCALAPPDATA || - process.env.APPDATA || - (os.homedir && os.homedir()) || - process.env.USERPROFILE || - (process.env.HOMEDRIVE && process.env.HOMEPATH && normalizeSlashes(process.env.HOMEDRIVE + process.env.HOMEPATH)) || - os.tmpdir(); - return combinePaths(combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"), versionMajorMinor); - } - case "openbsd": - case "freebsd": - case "netbsd": - case "darwin": - case "linux": - case "android": { - const cacheLocation = getNonWindowsCacheLocation(process.platform === "darwin"); - return combinePaths(combinePaths(cacheLocation, "typescript"), versionMajorMinor); - } - default: - return Debug.fail(`unsupported platform '${process.platform}'`); - } - } - - function getNonWindowsCacheLocation(platformIsDarwin: boolean) { - if (process.env.XDG_CACHE_HOME) { - return process.env.XDG_CACHE_HOME; - } - const usersDir = platformIsDarwin ? "Users" : "home"; - const homePath = (os.homedir && os.homedir()) || - process.env.HOME || - ((process.env.LOGNAME || process.env.USER) && `/${usersDir}/${process.env.LOGNAME || process.env.USER}`) || - os.tmpdir(); - const cacheFolder = platformIsDarwin - ? "Library/Caches" - : ".cache"; - return combinePaths(normalizeSlashes(homePath), cacheFolder); - } - - interface NodeChildProcess { - send(message: any, sendHandle?: any): void; - on(message: "message" | "exit", f: (m: any) => void): void; - kill(): void; - pid: number; - } - - interface ReadLineOptions { - input: NodeJS.ReadableStream; - output?: NodeJS.WritableStream; - terminal?: boolean; - historySize?: number; - } - - interface Stats { - isFile(): boolean; - isDirectory(): boolean; - isBlockDevice(): boolean; - isCharacterDevice(): boolean; - isSymbolicLink(): boolean; - isFIFO(): boolean; - isSocket(): boolean; - dev: number; - ino: number; - mode: number; - nlink: number; - uid: number; - gid: number; - rdev: number; - size: number; - blksize: number; - blocks: number; - atime: Date; - mtime: Date; - ctime: Date; - birthtime: Date; - } - - const readline: { - createInterface(options: ReadLineOptions): NodeJS.EventEmitter; - } = require("readline"); - - const fs: { - openSync(path: string, options: string): number; - close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void; - writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number; - writeSync(fd: number, data: any, position?: number, enconding?: string): number; - statSync(path: string): Stats; - stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; - } = require("fs"); - - - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false, - }); - - class Logger implements server.Logger { // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier - private fd = -1; - private seq = 0; - private inGroup = false; - private firstInGroup = true; - - constructor(private readonly logFilename: string, - private readonly traceToConsole: boolean, - private readonly level: LogLevel) { - if (this.logFilename) { - try { - this.fd = fs.openSync(this.logFilename, "w"); + if (require.main === module) { + const childProcess: { + fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; + execFileSync(file: string, args: string[], options: { stdio: "ignore", env: MapLike }): string | Buffer; + } = require("child_process"); + + const os: { + homedir?(): string; + tmpdir(): string; + platform(): string; + } = require("os"); + + interface NodeSocket { + write(data: string, encoding: string): boolean; + } + + const net: { + connect(options: { port: number }, onConnect?: () => void): NodeSocket + } = require("net"); + + const getGlobalTypingsCacheLocation = function () { + switch (process.platform) { + case "win32": { + const basePath = process.env.LOCALAPPDATA || + process.env.APPDATA || + (os.homedir && os.homedir()) || + process.env.USERPROFILE || + (process.env.HOMEDRIVE && process.env.HOMEPATH && normalizeSlashes(process.env.HOMEDRIVE + process.env.HOMEPATH)) || + os.tmpdir(); + return combinePaths(combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"), versionMajorMinor); } - catch (_) { - // swallow the error and keep logging disabled if file cannot be opened + case "openbsd": + case "freebsd": + case "netbsd": + case "darwin": + case "linux": + case "android": { + const cacheLocation = getNonWindowsCacheLocation(process.platform === "darwin"); + return combinePaths(combinePaths(cacheLocation, "typescript"), versionMajorMinor); + } + default: + return Debug.fail(`unsupported platform '${process.platform}'`); + } + } + + const getNonWindowsCacheLocation = function (platformIsDarwin: boolean) { + if (process.env.XDG_CACHE_HOME) { + return process.env.XDG_CACHE_HOME; + } + const usersDir = platformIsDarwin ? "Users" : "home"; + const homePath = (os.homedir && os.homedir()) || + process.env.HOME || + ((process.env.LOGNAME || process.env.USER) && `/${usersDir}/${process.env.LOGNAME || process.env.USER}`) || + os.tmpdir(); + const cacheFolder = platformIsDarwin + ? "Library/Caches" + : ".cache"; + return combinePaths(normalizeSlashes(homePath), cacheFolder); + } + + interface NodeChildProcess { + send(message: any, sendHandle?: any): void; + on(message: "message" | "exit", f: (m: any) => void): void; + kill(): void; + pid: number; + } + + interface ReadLineOptions { + input: NodeJS.ReadableStream; + output?: NodeJS.WritableStream; + terminal?: boolean; + historySize?: number; + } + + interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + blksize: number; + blocks: number; + atime: Date; + mtime: Date; + ctime: Date; + birthtime: Date; + } + + const readline: { + createInterface(options: ReadLineOptions): NodeJS.EventEmitter; + } = require("readline"); + + const fs: { + openSync(path: string, options: string): number; + close(fd: number, callback: (err: NodeJS.ErrnoException) => void): void; + writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number; + writeSync(fd: number, data: any, position?: number, enconding?: string): number; + statSync(path: string): Stats; + stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; + } = require("fs"); + + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false, + }); + + class Logger implements server.Logger { // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier + private fd = -1; + private seq = 0; + private inGroup = false; + private firstInGroup = true; + + constructor(private readonly logFilename: string, + private readonly traceToConsole: boolean, + private readonly level: LogLevel) { + if (this.logFilename) { + try { + this.fd = fs.openSync(this.logFilename, "w"); + } + catch (_) { + // swallow the error and keep logging disabled if file cannot be opened + } } } - } - static padStringRight(str: string, padding: string) { - return (str + padding).slice(0, padding.length); - } - - close() { - if (this.fd >= 0) { - fs.close(this.fd, noop); + static padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); } - } - - getLogFileName() { - return this.logFilename; - } - - perftrc(s: string) { - this.msg(s, Msg.Perf); - } - - info(s: string) { - this.msg(s, Msg.Info); - } - - err(s: string) { - this.msg(s, Msg.Err); - } - - startGroup() { - this.inGroup = true; - this.firstInGroup = true; - } - endGroup() { - this.inGroup = false; - } - - loggingEnabled() { - return !!this.logFilename || this.traceToConsole; - } + close() { + if (this.fd >= 0) { + fs.close(this.fd, noop); + } + } - hasLevel(level: LogLevel) { - return this.loggingEnabled() && this.level >= level; - } + getLogFileName() { + return this.logFilename; + } - msg(s: string, type: Msg = Msg.Err) { - switch (type) { - case Msg.Info: - perfLogger.logInfoEvent(s); - break; - case Msg.Perf: - perfLogger.logPerfEvent(s); - break; - default: // Msg.Err - perfLogger.logErrEvent(s); - break; + perftrc(s: string) { + this.msg(s, Msg.Perf); } - if (!this.canWrite) return; + info(s: string) { + this.msg(s, Msg.Info); + } - s = `[${nowString()}] ${s}\n`; - if (!this.inGroup || this.firstInGroup) { - const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); - s = prefix + s; + err(s: string) { + this.msg(s, Msg.Err); } - this.write(s); - if (!this.inGroup) { - this.seq++; + + startGroup() { + this.inGroup = true; + this.firstInGroup = true; } - } - private get canWrite() { - return this.fd >= 0 || this.traceToConsole; - } + endGroup() { + this.inGroup = false; + } - private write(s: string) { - if (this.fd >= 0) { - const buf = sys.bufferFrom!(s); - // eslint-disable-next-line no-null/no-null - fs.writeSync(this.fd, buf as globalThis.Buffer, 0, buf.length, /*position*/ null!); // TODO: GH#18217 + loggingEnabled() { + return !!this.logFilename || this.traceToConsole; } - if (this.traceToConsole) { - console.warn(s); + + hasLevel(level: LogLevel) { + return this.loggingEnabled() && this.level >= level; } - } - } - interface QueuedOperation { - operationId: string; - operation: () => void; - } + msg(s: string, type: Msg = Msg.Err) { + switch (type) { + case Msg.Info: + perfLogger.logInfoEvent(s); + break; + case Msg.Perf: + perfLogger.logPerfEvent(s); + break; + default: // Msg.Err + perfLogger.logErrEvent(s); + break; + } - class NodeTypingsInstaller implements ITypingsInstaller { - private installer!: NodeChildProcess; - private projectService!: ProjectService; - private activeRequestCount = 0; - private requestQueue: QueuedOperation[] = []; - private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID - /** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */ - private requestedRegistry = false; - private typesRegistryCache: Map> | undefined; - - // This number is essentially arbitrary. Processing more than one typings request - // at a time makes sense, but having too many in the pipe results in a hang - // (see https://github.com/nodejs/node/issues/7657). - // It would be preferable to base our limit on the amount of space left in the - // buffer, but we have yet to find a way to retrieve that value. - private static readonly maxActiveRequestCount = 10; - private static readonly requestDelayMillis = 100; - private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void, reject(reason: unknown): void } | undefined; - - constructor( - private readonly telemetryEnabled: boolean, - private readonly logger: Logger, - private readonly host: ServerHost, - readonly globalTypingsCacheLocation: string, - readonly typingSafeListLocation: string, - readonly typesMapLocation: string, - private readonly npmLocation: string | undefined, - private readonly validateDefaultNpmLocation: boolean, - private event: Event) { - } + if (!this.canWrite) return; - isKnownTypesPackageName(name: string): boolean { - // We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package. - const validationResult = JsTyping.validatePackageName(name); - if (validationResult !== JsTyping.NameValidationResult.Ok) { - return false; + s = `[${nowString()}] ${s}\n`; + if (!this.inGroup || this.firstInGroup) { + const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); + s = prefix + s; + } + this.write(s); + if (!this.inGroup) { + this.seq++; + } } - if (this.requestedRegistry) { - return !!this.typesRegistryCache && this.typesRegistryCache.has(name); + private get canWrite() { + return this.fd >= 0 || this.traceToConsole; } - this.requestedRegistry = true; - this.send({ kind: "typesRegistry" }); - return false; + private write(s: string) { + if (this.fd >= 0) { + const buf = sys.bufferFrom!(s); + // eslint-disable-next-line no-null/no-null + fs.writeSync(this.fd, buf as globalThis.Buffer, 0, buf.length, /*position*/ null!); // TODO: GH#18217 + } + if (this.traceToConsole) { + console.warn(s); + } + } } - installPackage(options: InstallPackageOptionsWithProject): Promise { - this.send({ kind: "installPackage", ...options }); - Debug.assert(this.packageInstalledPromise === undefined); - return new Promise((resolve, reject) => { - this.packageInstalledPromise = { resolve, reject }; - }); - } + interface QueuedOperation { + operationId: string; + operation: () => void; + } + + class NodeTypingsInstaller implements ITypingsInstaller { + private installer!: NodeChildProcess; + private projectService!: ProjectService; + private activeRequestCount = 0; + private requestQueue: QueuedOperation[] = []; + private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID + /** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */ + private requestedRegistry = false; + private typesRegistryCache: Map> | undefined; + + // This number is essentially arbitrary. Processing more than one typings request + // at a time makes sense, but having too many in the pipe results in a hang + // (see https://github.com/nodejs/node/issues/7657). + // It would be preferable to base our limit on the amount of space left in the + // buffer, but we have yet to find a way to retrieve that value. + private static readonly maxActiveRequestCount = 10; + private static readonly requestDelayMillis = 100; + private packageInstalledPromise: { resolve(value: ApplyCodeActionCommandResult): void, reject(reason: unknown): void } | undefined; + + constructor( + private readonly telemetryEnabled: boolean, + private readonly logger: Logger, + private readonly host: ServerHost, + readonly globalTypingsCacheLocation: string, + readonly typingSafeListLocation: string, + readonly typesMapLocation: string, + private readonly npmLocation: string | undefined, + private readonly validateDefaultNpmLocation: boolean, + private event: Event) { + } + + isKnownTypesPackageName(name: string): boolean { + // We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package. + const validationResult = JsTyping.validatePackageName(name); + if (validationResult !== JsTyping.NameValidationResult.Ok) { + return false; + } - attach(projectService: ProjectService) { - this.projectService = projectService; - if (this.logger.hasLevel(LogLevel.requestTime)) { - this.logger.info("Binding..."); - } + if (this.requestedRegistry) { + return !!this.typesRegistryCache && this.typesRegistryCache.has(name); + } - const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation]; - if (this.telemetryEnabled) { - args.push(Arguments.EnableTelemetry); - } - if (this.logger.loggingEnabled() && this.logger.getLogFileName()) { - args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`)); - } - if (this.typingSafeListLocation) { - args.push(Arguments.TypingSafeListLocation, this.typingSafeListLocation); - } - if (this.typesMapLocation) { - args.push(Arguments.TypesMapLocation, this.typesMapLocation); - } - if (this.npmLocation) { - args.push(Arguments.NpmLocation, this.npmLocation); + this.requestedRegistry = true; + this.send({ kind: "typesRegistry" }); + return false; } - if (this.validateDefaultNpmLocation) { - args.push(Arguments.ValidateDefaultNpmLocation); + + installPackage(options: InstallPackageOptionsWithProject): Promise { + this.send({ kind: "installPackage", ...options }); + Debug.assert(this.packageInstalledPromise === undefined); + return new Promise((resolve, reject) => { + this.packageInstalledPromise = { resolve, reject }; + }); } - const execArgv: string[] = []; - for (const arg of process.execArgv) { - const match = /^--((?:debug|inspect)(?:-brk)?)(?:=(\d+))?$/.exec(arg); - if (match) { - // if port is specified - use port + 1 - // otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1 - const currentPort = match[2] !== undefined - ? +match[2] - : match[1].charAt(0) === "d" ? 5858 : 9229; - execArgv.push(`--${match[1]}=${currentPort + 1}`); - break; + attach(projectService: ProjectService) { + this.projectService = projectService; + if (this.logger.hasLevel(LogLevel.requestTime)) { + this.logger.info("Binding..."); } - } - this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args, { execArgv }); - this.installer.on("message", m => this.handleMessage(m)); + const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation]; + if (this.telemetryEnabled) { + args.push(Arguments.EnableTelemetry); + } + if (this.logger.loggingEnabled() && this.logger.getLogFileName()) { + args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`)); + } + if (this.typingSafeListLocation) { + args.push(Arguments.TypingSafeListLocation, this.typingSafeListLocation); + } + if (this.typesMapLocation) { + args.push(Arguments.TypesMapLocation, this.typesMapLocation); + } + if (this.npmLocation) { + args.push(Arguments.NpmLocation, this.npmLocation); + } + if (this.validateDefaultNpmLocation) { + args.push(Arguments.ValidateDefaultNpmLocation); + } - this.event({ pid: this.installer.pid }, "typingsInstallerPid"); + const execArgv: string[] = []; + for (const arg of process.execArgv) { + const match = /^--((?:debug|inspect)(?:-brk)?)(?:=(\d+))?$/.exec(arg); + if (match) { + // if port is specified - use port + 1 + // otherwise pick a default port depending on if 'debug' or 'inspect' and use its value + 1 + const currentPort = match[2] !== undefined + ? +match[2] + : match[1].charAt(0) === "d" ? 5858 : 9229; + execArgv.push(`--${match[1]}=${currentPort + 1}`); + break; + } + } - process.on("exit", () => { - this.installer.kill(); - }); - } + this.installer = childProcess.fork(combinePaths(__dirname, "typingsInstaller.js"), args, { execArgv }); + this.installer.on("message", m => this.handleMessage(m)); - onProjectClosed(p: Project): void { - this.send({ projectName: p.getProjectName(), kind: "closeProject" }); - } + this.event({ pid: this.installer.pid }, "typingsInstallerPid"); - private send(rq: T): void { - this.installer.send(rq); - } + process.on("exit", () => { + this.installer.kill(); + }); + } - enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray): void { - const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports); - if (this.logger.hasLevel(LogLevel.verbose)) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Scheduling throttled operation:${stringifyIndented(request)}`); - } + onProjectClosed(p: Project): void { + this.send({ projectName: p.getProjectName(), kind: "closeProject" }); + } + + private send(rq: T): void { + this.installer.send(rq); } - const operationId = project.getProjectName(); - const operation = () => { + enqueueInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray): void { + const request = createInstallTypingsRequest(project, typeAcquisition, unresolvedImports); if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Sending request:${stringifyIndented(request)}`); + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Scheduling throttled operation:${stringifyIndented(request)}`); + } } - this.send(request); - }; - const queuedRequest: QueuedOperation = { operationId, operation }; - if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) { - this.scheduleRequest(queuedRequest); + const operationId = project.getProjectName(); + const operation = () => { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Sending request:${stringifyIndented(request)}`); + } + this.send(request); + }; + const queuedRequest: QueuedOperation = { operationId, operation }; + + if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) { + this.scheduleRequest(queuedRequest); + } + else { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Deferring request for: ${operationId}`); + } + this.requestQueue.push(queuedRequest); + this.requestMap.set(operationId, queuedRequest); + } } - else { + + private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) { if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Deferring request for: ${operationId}`); + this.logger.info(`Received response:${stringifyIndented(response)}`); } - this.requestQueue.push(queuedRequest); - this.requestMap.set(operationId, queuedRequest); - } - } - private handleMessage(response: TypesRegistryResponse | PackageInstalledResponse | SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Received response:${stringifyIndented(response)}`); - } + switch (response.kind) { + case EventTypesRegistry: + this.typesRegistryCache = createMapFromTemplate(response.typesRegistry); + break; + case ActionPackageInstalled: { + const { success, message } = response; + if (success) { + this.packageInstalledPromise!.resolve({ successMessage: message }); + } + else { + this.packageInstalledPromise!.reject(message); + } + this.packageInstalledPromise = undefined; + + this.projectService.updateTypingsForProject(response); - switch (response.kind) { - case EventTypesRegistry: - this.typesRegistryCache = createMapFromTemplate(response.typesRegistry); - break; - case ActionPackageInstalled: { - const { success, message } = response; - if (success) { - this.packageInstalledPromise!.resolve({ successMessage: message }); + // The behavior is the same as for setTypings, so send the same event. + this.event(response, "setTypings"); + break; } - else { - this.packageInstalledPromise!.reject(message); + case EventInitializationFailed: { + const body: protocol.TypesInstallerInitializationFailedEventBody = { + message: response.message + }; + const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed"; + this.event(body, eventName); + break; } - this.packageInstalledPromise = undefined; - - this.projectService.updateTypingsForProject(response); - - // The behavior is the same as for setTypings, so send the same event. - this.event(response, "setTypings"); - break; - } - case EventInitializationFailed: { - const body: protocol.TypesInstallerInitializationFailedEventBody = { - message: response.message - }; - const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed"; - this.event(body, eventName); - break; - } - case EventBeginInstallTypes: { - const body: protocol.BeginInstallTypesEventBody = { - eventId: response.eventId, - packages: response.packagesToInstall, - }; - const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes"; - this.event(body, eventName); - break; - } - case EventEndInstallTypes: { - if (this.telemetryEnabled) { - const body: protocol.TypingsInstalledTelemetryEventBody = { - telemetryEventName: "typingsInstalled", - payload: { - installedPackages: response.packagesToInstall.join(","), - installSuccess: response.installSuccess, - typingsInstallerVersion: response.typingsInstallerVersion - } + case EventBeginInstallTypes: { + const body: protocol.BeginInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, }; - const eventName: protocol.TelemetryEventName = "telemetry"; + const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes"; this.event(body, eventName); + break; } + case EventEndInstallTypes: { + if (this.telemetryEnabled) { + const body: protocol.TypingsInstalledTelemetryEventBody = { + telemetryEventName: "typingsInstalled", + payload: { + installedPackages: response.packagesToInstall.join(","), + installSuccess: response.installSuccess, + typingsInstallerVersion: response.typingsInstallerVersion + } + }; + const eventName: protocol.TelemetryEventName = "telemetry"; + this.event(body, eventName); + } - const body: protocol.EndInstallTypesEventBody = { - eventId: response.eventId, - packages: response.packagesToInstall, - success: response.installSuccess, - }; - const eventName: protocol.EndInstallTypesEventName = "endInstallTypes"; - this.event(body, eventName); - break; - } - case ActionInvalidate: { - this.projectService.updateTypingsForProject(response); - break; - } - case ActionSet: { - if (this.activeRequestCount > 0) { - this.activeRequestCount--; + const body: protocol.EndInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, + success: response.installSuccess, + }; + const eventName: protocol.EndInstallTypesEventName = "endInstallTypes"; + this.event(body, eventName); + break; } - else { - Debug.fail("Received too many responses"); + case ActionInvalidate: { + this.projectService.updateTypingsForProject(response); + break; } - - while (this.requestQueue.length > 0) { - const queuedRequest = this.requestQueue.shift()!; - if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) { - this.requestMap.delete(queuedRequest.operationId); - this.scheduleRequest(queuedRequest); - break; + case ActionSet: { + if (this.activeRequestCount > 0) { + this.activeRequestCount--; + } + else { + Debug.fail("Received too many responses"); } - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`); + while (this.requestQueue.length > 0) { + const queuedRequest = this.requestQueue.shift()!; + if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) { + this.requestMap.delete(queuedRequest.operationId); + this.scheduleRequest(queuedRequest); + break; + } + + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`); + } } - } - this.projectService.updateTypingsForProject(response); + this.projectService.updateTypingsForProject(response); - this.event(response, "setTypings"); + this.event(response, "setTypings"); - break; + break; + } + default: + assertType(response); } - default: - assertType(response); } - } - private scheduleRequest(request: QueuedOperation) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Scheduling request for: ${request.operationId}`); + private scheduleRequest(request: QueuedOperation) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Scheduling request for: ${request.operationId}`); + } + this.activeRequestCount++; + this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis); } - this.activeRequestCount++; - this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis); } - } - - class IOSession extends Session { - private eventPort: number | undefined; - private eventSocket: NodeSocket | undefined; - private socketEventQueue: { body: any, eventName: string }[] | undefined; - private constructed: boolean | undefined; - constructor() { - const event: Event | undefined = (body: object, eventName: string) => { - if (this.constructed) { - this.event(body, eventName); - } - else { - // It is unsafe to dereference `this` before initialization completes, - // so we defer until the next tick. - // - // Construction should finish before the next tick fires, so we do not need to do this recursively. - // eslint-disable-next-line no-restricted-globals - setImmediate(() => this.event(body, eventName)); - } - }; + class IOSession extends Session { + private eventPort: number | undefined; + private eventSocket: NodeSocket | undefined; + private socketEventQueue: { body: any, eventName: string }[] | undefined; + private constructed: boolean | undefined; - const host = sys; - - const typingsInstaller = disableAutomaticTypingAcquisition - ? undefined - : new NodeTypingsInstaller(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, validateDefaultNpmLocation, event); - - super({ - host, - cancellationToken, - useSingleInferredProject, - useInferredProjectPerProjectRoot, - typingsInstaller: typingsInstaller || nullTypingsInstaller, - byteLength: Buffer.byteLength, - hrtime: process.hrtime, - logger, - canUseEvents: true, - suppressDiagnosticEvents, - syntaxOnly, - noGetErrOnBackgroundUpdate, - globalPlugins, - pluginProbeLocations, - allowLocalPluginLoads, - typesMapLocation, - }); - - this.eventPort = eventPort; - if (this.canUseEvents && this.eventPort) { - const s = net.connect({ port: this.eventPort }, () => { - this.eventSocket = s; - if (this.socketEventQueue) { - // flush queue. - for (const event of this.socketEventQueue) { - this.writeToEventSocket(event.body, event.eventName); - } - this.socketEventQueue = undefined; + constructor() { + const event: Event | undefined = (body: object, eventName: string) => { + if (this.constructed) { + this.event(body, eventName); + } + else { + // It is unsafe to dereference `this` before initialization completes, + // so we defer until the next tick. + // + // Construction should finish before the next tick fires, so we do not need to do this recursively. + // eslint-disable-next-line no-restricted-globals + setImmediate(() => this.event(body, eventName)); } + }; + + const host = sys; + + const typingsInstaller = disableAutomaticTypingAcquisition + ? undefined + : new NodeTypingsInstaller(telemetryEnabled, logger, host, getGlobalTypingsCacheLocation(), typingSafeListLocation, typesMapLocation, npmLocation, validateDefaultNpmLocation, event); + + super({ + host, + cancellationToken, + useSingleInferredProject, + useInferredProjectPerProjectRoot, + typingsInstaller: typingsInstaller || nullTypingsInstaller, + byteLength: Buffer.byteLength, + hrtime: process.hrtime, + logger, + canUseEvents: true, + suppressDiagnosticEvents, + syntaxOnly, + noGetErrOnBackgroundUpdate, + globalPlugins, + pluginProbeLocations, + allowLocalPluginLoads, + typesMapLocation, }); - } - this.constructed = true; - } + this.eventPort = eventPort; + if (this.canUseEvents && this.eventPort) { + const s = net.connect({ port: this.eventPort }, () => { + this.eventSocket = s; + if (this.socketEventQueue) { + // flush queue. + for (const event of this.socketEventQueue) { + this.writeToEventSocket(event.body, event.eventName); + } + this.socketEventQueue = undefined; + } + }); + } - event(body: T, eventName: string): void { - Debug.assert(!!this.constructed, "Should only call `IOSession.prototype.event` on an initialized IOSession"); + this.constructed = true; + } - if (this.canUseEvents && this.eventPort) { - if (!this.eventSocket) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`); + event(body: T, eventName: string): void { + Debug.assert(!!this.constructed, "Should only call `IOSession.prototype.event` on an initialized IOSession"); + + if (this.canUseEvents && this.eventPort) { + if (!this.eventSocket) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`eventPort: event "${eventName}" queued, but socket not yet initialized`); + } + (this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName }); + return; + } + else { + Debug.assert(this.socketEventQueue === undefined); + this.writeToEventSocket(body, eventName); } - (this.socketEventQueue || (this.socketEventQueue = [])).push({ body, eventName }); - return; } else { - Debug.assert(this.socketEventQueue === undefined); - this.writeToEventSocket(body, eventName); + super.event(body, eventName); } } - else { - super.event(body, eventName); - } - } - private writeToEventSocket(body: object, eventName: string): void { - this.eventSocket!.write(formatMessage(toEvent(eventName, body), this.logger, this.byteLength, this.host.newLine), "utf8"); - } + private writeToEventSocket(body: object, eventName: string): void { + this.eventSocket!.write(formatMessage(toEvent(eventName, body), this.logger, this.byteLength, this.host.newLine), "utf8"); + } - exit() { - this.logger.info("Exiting..."); - this.projectService.closeLog(); - process.exit(0); - } + exit() { + this.logger.info("Exiting..."); + this.projectService.closeLog(); + process.exit(0); + } - listen() { - rl.on("line", (input: string) => { - const message = input.trim(); - this.onMessage(message); - }); + listen() { + rl.on("line", (input: string) => { + const message = input.trim(); + this.onMessage(message); + }); - rl.on("close", () => { - this.exit(); - }); + rl.on("close", () => { + this.exit(); + }); + } } - } - interface LogOptions { - file?: string; - detailLevel?: LogLevel; - traceToConsole?: boolean; - logToFile?: boolean; - } - - function parseLoggingEnvironmentString(logEnvStr: string | undefined): LogOptions { - if (!logEnvStr) { - return {}; + interface LogOptions { + file?: string; + detailLevel?: LogLevel; + traceToConsole?: boolean; + logToFile?: boolean; } - const logEnv: LogOptions = { logToFile: true }; - const args = logEnvStr.split(" "); - const len = args.length - 1; - for (let i = 0; i < len; i += 2) { - const option = args[i]; - const { value, extraPartCounter } = getEntireValue(i + 1); - i += extraPartCounter; - if (option && value) { - switch (option) { - case "-file": - logEnv.file = value; - break; - case "-level": - const level = getLogLevel(value); - logEnv.detailLevel = level !== undefined ? level : LogLevel.normal; - break; - case "-traceToConsole": - logEnv.traceToConsole = value.toLowerCase() === "true"; - break; - case "-logToFile": - logEnv.logToFile = value.toLowerCase() === "true"; - break; + + const parseLoggingEnvironmentString = function (logEnvStr: string | undefined): LogOptions { + if (!logEnvStr) { + return {}; + } + const logEnv: LogOptions = { logToFile: true }; + const args = logEnvStr.split(" "); + const len = args.length - 1; + for (let i = 0; i < len; i += 2) { + const option = args[i]; + const { value, extraPartCounter } = getEntireValue(i + 1); + i += extraPartCounter; + if (option && value) { + switch (option) { + case "-file": + logEnv.file = value; + break; + case "-level": + const level = getLogLevel(value); + logEnv.detailLevel = level !== undefined ? level : LogLevel.normal; + break; + case "-traceToConsole": + logEnv.traceToConsole = value.toLowerCase() === "true"; + break; + case "-logToFile": + logEnv.logToFile = value.toLowerCase() === "true"; + break; + } } } - } - return logEnv; - - function getEntireValue(initialIndex: number) { - let pathStart = args[initialIndex]; - let extraPartCounter = 0; - if (pathStart.charCodeAt(0) === CharacterCodes.doubleQuote && - pathStart.charCodeAt(pathStart.length - 1) !== CharacterCodes.doubleQuote) { - for (let i = initialIndex + 1; i < args.length; i++) { - pathStart += " "; - pathStart += args[i]; - extraPartCounter++; - if (pathStart.charCodeAt(pathStart.length - 1) === CharacterCodes.doubleQuote) break; + return logEnv; + + function getEntireValue(initialIndex: number) { + let pathStart = args[initialIndex]; + let extraPartCounter = 0; + if (pathStart.charCodeAt(0) === CharacterCodes.doubleQuote && + pathStart.charCodeAt(pathStart.length - 1) !== CharacterCodes.doubleQuote) { + for (let i = initialIndex + 1; i < args.length; i++) { + pathStart += " "; + pathStart += args[i]; + extraPartCounter++; + if (pathStart.charCodeAt(pathStart.length - 1) === CharacterCodes.doubleQuote) break; + } } + return { value: stripQuotes(pathStart), extraPartCounter }; } - return { value: stripQuotes(pathStart), extraPartCounter }; } - } - function getLogLevel(level: string | undefined) { - if (level) { - const l = level.toLowerCase(); - for (const name in LogLevel) { - if (isNaN(+name) && l === name.toLowerCase()) { - return LogLevel[name]; + const getLogLevel = function (level: string | undefined) { + if (level) { + const l = level.toLowerCase(); + for (const name in LogLevel) { + if (isNaN(+name) && l === name.toLowerCase()) { + return LogLevel[name]; + } } } + return undefined; } - return undefined; - } - // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" - function createLogger() { - const cmdLineLogFileName = findArgument("--logFile"); - const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity")); - const envLogOptions = parseLoggingEnvironmentString(process.env.TSS_LOG); + // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" + const createLogger = function () { + const cmdLineLogFileName = findArgument("--logFile"); + const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity")); + const envLogOptions = parseLoggingEnvironmentString(process.env.TSS_LOG); - const unsubstitutedLogFileName = cmdLineLogFileName - ? stripQuotes(cmdLineLogFileName) - : envLogOptions.logToFile - ? envLogOptions.file || (__dirname + "/.log" + process.pid.toString()) - : undefined; + const unsubstitutedLogFileName = cmdLineLogFileName + ? stripQuotes(cmdLineLogFileName) + : envLogOptions.logToFile + ? envLogOptions.file || (__dirname + "/.log" + process.pid.toString()) + : undefined; - const substitutedLogFileName = unsubstitutedLogFileName - ? unsubstitutedLogFileName.replace("PID", process.pid.toString()) - : undefined; + const substitutedLogFileName = unsubstitutedLogFileName + ? unsubstitutedLogFileName.replace("PID", process.pid.toString()) + : undefined; - const logVerbosity = cmdLineVerbosity || envLogOptions.detailLevel; - return new Logger(substitutedLogFileName!, envLogOptions.traceToConsole!, logVerbosity!); // TODO: GH#18217 - } - // This places log file in the directory containing editorServices.js - // TODO: check that this location is writable - - // average async stat takes about 30 microseconds - // set chunk size to do 30 files in < 1 millisecond - function createPollingWatchedFileSet(interval = 2500, chunkSize = 30) { - const watchedFiles: WatchedFile[] = []; - let nextFileToCheck = 0; - return { getModifiedTime, poll, startWatchTimer, addFile, removeFile }; - - function getModifiedTime(fileName: string): Date { - return fs.statSync(fileName).mtime; + const logVerbosity = cmdLineVerbosity || envLogOptions.detailLevel; + return new Logger(substitutedLogFileName!, envLogOptions.traceToConsole!, logVerbosity!); // TODO: GH#18217 } + // This places log file in the directory containing editorServices.js + // TODO: check that this location is writable + + // average async stat takes about 30 microseconds + // set chunk size to do 30 files in < 1 millisecond + const createPollingWatchedFileSet = function (interval = 2500, chunkSize = 30) { + const watchedFiles: WatchedFile[] = []; + let nextFileToCheck = 0; + return { getModifiedTime, poll, startWatchTimer, addFile, removeFile }; - function poll(checkedIndex: number) { - const watchedFile = watchedFiles[checkedIndex]; - if (!watchedFile) { - return; + function getModifiedTime(fileName: string): Date { + return fs.statSync(fileName).mtime; } - fs.stat(watchedFile.fileName, (err, stats) => { - if (err) { - if (err.code === "ENOENT") { - if (watchedFile.mtime.getTime() !== 0) { - watchedFile.mtime = missingFileModifiedTime; - watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Deleted); + function poll(checkedIndex: number) { + const watchedFile = watchedFiles[checkedIndex]; + if (!watchedFile) { + return; + } + + fs.stat(watchedFile.fileName, (err, stats) => { + if (err) { + if (err.code === "ENOENT") { + if (watchedFile.mtime.getTime() !== 0) { + watchedFile.mtime = missingFileModifiedTime; + watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Deleted); + } + } + else { + watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed); } } else { - watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed); + onWatchedFileStat(watchedFile, stats.mtime); } - } - else { - onWatchedFileStat(watchedFile, stats.mtime); - } - }); - } + }); + } - // this implementation uses polling and - // stat due to inconsistencies of fs.watch - // and efficiency of stat on modern filesystems - function startWatchTimer() { - // eslint-disable-next-line no-restricted-globals - setInterval(() => { - let count = 0; - let nextToCheck = nextFileToCheck; - let firstCheck = -1; - while ((count < chunkSize) && (nextToCheck !== firstCheck)) { - poll(nextToCheck); - if (firstCheck < 0) { - firstCheck = nextToCheck; - } - nextToCheck++; - if (nextToCheck === watchedFiles.length) { - nextToCheck = 0; + // this implementation uses polling and + // stat due to inconsistencies of fs.watch + // and efficiency of stat on modern filesystems + function startWatchTimer() { + // eslint-disable-next-line no-restricted-globals + setInterval(() => { + let count = 0; + let nextToCheck = nextFileToCheck; + let firstCheck = -1; + while ((count < chunkSize) && (nextToCheck !== firstCheck)) { + poll(nextToCheck); + if (firstCheck < 0) { + firstCheck = nextToCheck; + } + nextToCheck++; + if (nextToCheck === watchedFiles.length) { + nextToCheck = 0; + } + count++; } - count++; + nextFileToCheck = nextToCheck; + }, interval); + } + + function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile { + const file: WatchedFile = { + fileName, + callback, + mtime: sys.fileExists(fileName) + ? getModifiedTime(fileName) + : missingFileModifiedTime // Any subsequent modification will occur after this time + }; + + watchedFiles.push(file); + if (watchedFiles.length === 1) { + startWatchTimer(); } - nextFileToCheck = nextToCheck; - }, interval); - } - - function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile { - const file: WatchedFile = { - fileName, - callback, - mtime: sys.fileExists(fileName) - ? getModifiedTime(fileName) - : missingFileModifiedTime // Any subsequent modification will occur after this time - }; + return file; + } - watchedFiles.push(file); - if (watchedFiles.length === 1) { - startWatchTimer(); + function removeFile(file: WatchedFile) { + unorderedRemoveItem(watchedFiles, file); } - return file; } - function removeFile(file: WatchedFile) { - unorderedRemoveItem(watchedFiles, file); - } - } + // REVIEW: for now this implementation uses polling. + // The advantage of polling is that it works reliably + // on all os and with network mounted files. + // For 90 referenced files, the average time to detect + // changes is 2*msInterval (by default 5 seconds). + // The overhead of this is .04 percent (1/2500) with + // average pause of < 1 millisecond (and max + // pause less than 1.5 milliseconds); question is + // do we anticipate reference sets in the 100s and + // do we care about waiting 10-20 seconds to detect + // changes for large reference sets? If so, do we want + // to increase the chunk size or decrease the interval + // time dynamically to match the large reference set? + const pollingWatchedFileSet = createPollingWatchedFileSet(); - // REVIEW: for now this implementation uses polling. - // The advantage of polling is that it works reliably - // on all os and with network mounted files. - // For 90 referenced files, the average time to detect - // changes is 2*msInterval (by default 5 seconds). - // The overhead of this is .04 percent (1/2500) with - // average pause of < 1 millisecond (and max - // pause less than 1.5 milliseconds); question is - // do we anticipate reference sets in the 100s and - // do we care about waiting 10-20 seconds to detect - // changes for large reference sets? If so, do we want - // to increase the chunk size or decrease the interval - // time dynamically to match the large reference set? - const pollingWatchedFileSet = createPollingWatchedFileSet(); - - const pending: Buffer[] = []; - let canWrite = true; - - function writeMessage(buf: Buffer) { - if (!canWrite) { - pending.push(buf); - } - else { - canWrite = false; - process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary); - } - } + const pending: Buffer[] = []; + let canWrite = true; - function setCanWriteFlagAndWriteMessageIfNecessary() { - canWrite = true; - if (pending.length) { - writeMessage(pending.shift()!); + const writeMessage = function (buf: Buffer) { + if (!canWrite) { + pending.push(buf); + } + else { + canWrite = false; + process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary); + } } - } - function extractWatchDirectoryCacheKey(path: string, currentDriveKey: string | undefined) { - path = normalizeSlashes(path); - if (isUNCPath(path)) { - // UNC path: extract server name - // //server/location - // ^ <- from 0 to this position - const firstSlash = path.indexOf(directorySeparator, 2); - return firstSlash !== -1 ? toFileNameLowerCase(path.substring(0, firstSlash)) : path; - } - const rootLength = getRootLength(path); - if (rootLength === 0) { - // relative path - assume file is on the current drive - return currentDriveKey; + const setCanWriteFlagAndWriteMessageIfNecessary = function () { + canWrite = true; + if (pending.length) { + writeMessage(pending.shift()!); + } } - if (path.charCodeAt(1) === CharacterCodes.colon && path.charCodeAt(2) === CharacterCodes.slash) { - // rooted path that starts with c:/... - extract drive letter - return toFileNameLowerCase(path.charAt(0)); + + const extractWatchDirectoryCacheKey = function (path: string, currentDriveKey: string | undefined) { + path = normalizeSlashes(path); + if (isUNCPath(path)) { + // UNC path: extract server name + // //server/location + // ^ <- from 0 to this position + const firstSlash = path.indexOf(directorySeparator, 2); + return firstSlash !== -1 ? toFileNameLowerCase(path.substring(0, firstSlash)) : path; + } + const rootLength = getRootLength(path); + if (rootLength === 0) { + // relative path - assume file is on the current drive + return currentDriveKey; + } + if (path.charCodeAt(1) === CharacterCodes.colon && path.charCodeAt(2) === CharacterCodes.slash) { + // rooted path that starts with c:/... - extract drive letter + return toFileNameLowerCase(path.charAt(0)); + } + if (path.charCodeAt(0) === CharacterCodes.slash && path.charCodeAt(1) !== CharacterCodes.slash) { + // rooted path that starts with slash - /somename - use key for current drive + return currentDriveKey; + } + // do not cache any other cases + return undefined; } - if (path.charCodeAt(0) === CharacterCodes.slash && path.charCodeAt(1) !== CharacterCodes.slash) { - // rooted path that starts with slash - /somename - use key for current drive - return currentDriveKey; + + const isUNCPath = function (s: string): boolean { + return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash; } - // do not cache any other cases - return undefined; - } - function isUNCPath(s: string): boolean { - return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash; - } + const logger = createLogger(); - const logger = createLogger(); - - const sys = ts.sys; - const nodeVersion = getNodeMajorVersion(); - // use watchGuard process on Windows when node version is 4 or later - const useWatchGuard = process.platform === "win32" && nodeVersion! >= 4; - const originalWatchDirectory: ServerHost["watchDirectory"] = sys.watchDirectory.bind(sys); - const noopWatcher: FileWatcher = { close: noop }; - // This is the function that catches the exceptions when watching directory, and yet lets project service continue to function - // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - function watchDirectorySwallowingException(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher { - try { - return originalWatchDirectory(path, callback, recursive, options); - } - catch (e) { - logger.info(`Exception when creating directory watcher: ${e.message}`); - return noopWatcher; + const sys = ts.sys; + const nodeVersion = getNodeMajorVersion(); + // use watchGuard process on Windows when node version is 4 or later + const useWatchGuard = process.platform === "win32" && nodeVersion! >= 4; + const originalWatchDirectory: ServerHost["watchDirectory"] = sys.watchDirectory.bind(sys); + const noopWatcher: FileWatcher = { close: noop }; + // This is the function that catches the exceptions when watching directory, and yet lets project service continue to function + // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point + const watchDirectorySwallowingException = function (path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher { + try { + return originalWatchDirectory(path, callback, recursive, options); + } + catch (e) { + logger.info(`Exception when creating directory watcher: ${e.message}`); + return noopWatcher; + } } - } - if (useWatchGuard) { - const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined); - const statusCache = createMap(); - sys.watchDirectory = (path, callback, recursive, options) => { - const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive); - let status = cacheKey && statusCache.get(cacheKey); - if (status === undefined) { - if (logger.hasLevel(LogLevel.verbose)) { - logger.info(`${cacheKey} for path ${path} not found in cache...`); - } - try { - const args = [combinePaths(__dirname, "watchGuard.js"), path]; + if (useWatchGuard) { + const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined); + const statusCache = createMap(); + sys.watchDirectory = (path, callback, recursive, options) => { + const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive); + let status = cacheKey && statusCache.get(cacheKey); + if (status === undefined) { if (logger.hasLevel(LogLevel.verbose)) { - logger.info(`Starting ${process.execPath} with args:${stringifyIndented(args)}`); + logger.info(`${cacheKey} for path ${path} not found in cache...`); } - childProcess.execFileSync(process.execPath, args, { stdio: "ignore", env: { ELECTRON_RUN_AS_NODE: "1" } }); - status = true; - if (logger.hasLevel(LogLevel.verbose)) { - logger.info(`WatchGuard for path ${path} returned: OK`); + try { + const args = [combinePaths(__dirname, "watchGuard.js"), path]; + if (logger.hasLevel(LogLevel.verbose)) { + logger.info(`Starting ${process.execPath} with args:${stringifyIndented(args)}`); + } + childProcess.execFileSync(process.execPath, args, { stdio: "ignore", env: { ELECTRON_RUN_AS_NODE: "1" } }); + status = true; + if (logger.hasLevel(LogLevel.verbose)) { + logger.info(`WatchGuard for path ${path} returned: OK`); + } } - } - catch (e) { - status = false; - if (logger.hasLevel(LogLevel.verbose)) { - logger.info(`WatchGuard for path ${path} returned: ${e.message}`); + catch (e) { + status = false; + if (logger.hasLevel(LogLevel.verbose)) { + logger.info(`WatchGuard for path ${path} returned: ${e.message}`); + } + } + if (cacheKey) { + statusCache.set(cacheKey, status); } } - if (cacheKey) { - statusCache.set(cacheKey, status); + else if (logger.hasLevel(LogLevel.verbose)) { + logger.info(`watchDirectory for ${path} uses cached drive information.`); } - } - else if (logger.hasLevel(LogLevel.verbose)) { - logger.info(`watchDirectory for ${path} uses cached drive information.`); - } - if (status) { - // this drive is safe to use - call real 'watchDirectory' - return watchDirectorySwallowingException(path, callback, recursive, options); - } - else { - // this drive is unsafe - return no-op watcher - return noopWatcher; - } - }; - } - else { - sys.watchDirectory = watchDirectorySwallowingException; - } + if (status) { + // this drive is safe to use - call real 'watchDirectory' + return watchDirectorySwallowingException(path, callback, recursive, options); + } + else { + // this drive is unsafe - return no-op watcher + return noopWatcher; + } + }; + } + else { + sys.watchDirectory = watchDirectorySwallowingException; + } - // Override sys.write because fs.writeSync is not reliable on Node 4 - sys.write = (s: string) => writeMessage(sys.bufferFrom!(s, "utf8") as globalThis.Buffer); - sys.watchFile = (fileName, callback) => { - const watchedFile = pollingWatchedFileSet.addFile(fileName, callback); - return { - close: () => pollingWatchedFileSet.removeFile(watchedFile) + // Override sys.write because fs.writeSync is not reliable on Node 4 + sys.write = (s: string) => writeMessage(sys.bufferFrom!(s, "utf8") as globalThis.Buffer); + sys.watchFile = (fileName, callback) => { + const watchedFile = pollingWatchedFileSet.addFile(fileName, callback); + return { + close: () => pollingWatchedFileSet.removeFile(watchedFile) + }; }; - }; - /* eslint-disable no-restricted-globals */ - sys.setTimeout = setTimeout; - sys.clearTimeout = clearTimeout; - sys.setImmediate = setImmediate; - sys.clearImmediate = clearImmediate; - /* eslint-enable no-restricted-globals */ + /* eslint-disable no-restricted-globals */ + sys.setTimeout = setTimeout; + sys.clearTimeout = clearTimeout; + sys.setImmediate = setImmediate; + sys.clearImmediate = clearImmediate; + /* eslint-enable no-restricted-globals */ - if (typeof global !== "undefined" && global.gc) { - sys.gc = () => global.gc(); - } + if (typeof global !== "undefined" && global.gc) { + sys.gc = () => global.gc(); + } + + sys.require = (initialDir: string, moduleName: string): RequireResult => { + try { + return { module: require(resolveJSModule(moduleName, initialDir, sys)), error: undefined }; + } + catch (error) { + return { module: undefined, error }; + } + }; - sys.require = (initialDir: string, moduleName: string): RequireResult => { + let cancellationToken: ServerCancellationToken; try { - return { module: require(resolveJSModule(moduleName, initialDir, sys)), error: undefined }; + const factory = require("./cancellationToken"); + cancellationToken = factory(sys.args); } - catch (error) { - return { module: undefined, error }; + catch (e) { + cancellationToken = nullCancellationToken; } - }; - let cancellationToken: ServerCancellationToken; - try { - const factory = require("./cancellationToken"); - cancellationToken = factory(sys.args); - } - catch (e) { - cancellationToken = nullCancellationToken; - } + const parseEventPort = function (eventPortStr: string | undefined) { + const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr); + return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; + } + const eventPort: number | undefined = parseEventPort(findArgument("--eventPort")); - function parseEventPort(eventPortStr: string | undefined) { - const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr); - return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; - } - const eventPort: number | undefined = parseEventPort(findArgument("--eventPort")); + const localeStr = findArgument("--locale"); + if (localeStr) { + validateLocaleAndSetLanguage(localeStr, sys); + } - const localeStr = findArgument("--locale"); - if (localeStr) { - validateLocaleAndSetLanguage(localeStr, sys); - } + setStackTraceLimit(); + + const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation)!; // TODO: GH#18217 + const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typesMap.json"); + const npmLocation = findArgument(Arguments.NpmLocation); + const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); - setStackTraceLimit(); + const parseStringArray = function (argName: string): readonly string[] { + const arg = findArgument(argName); + if (arg === undefined) { + return emptyArray; + } + return arg.split(",").filter(name => name !== ""); + } + + const globalPlugins = parseStringArray("--globalPlugins"); + const pluginProbeLocations = parseStringArray("--pluginProbeLocations"); + const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads"); + + const useSingleInferredProject = hasArgument("--useSingleInferredProject"); + const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot"); + const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); + const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents"); + const syntaxOnly = hasArgument("--syntaxOnly"); + const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); + const noGetErrOnBackgroundUpdate = hasArgument("--noGetErrOnBackgroundUpdate"); + + logger.info(`Starting TS Server`); + logger.info(`Version: ${version}`); + logger.info(`Arguments: ${process.argv.join(" ")}`); + logger.info(`Platform: ${os.platform()} NodeVersion: ${nodeVersion} CaseSensitive: ${sys.useCaseSensitiveFileNames}`); - const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation)!; // TODO: GH#18217 - const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typesMap.json"); - const npmLocation = findArgument(Arguments.NpmLocation); - const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); + const ioSession = new IOSession(); + process.on("uncaughtException", err => { + ioSession.logError(err, "unknown"); + }); + // See https://github.com/Microsoft/TypeScript/issues/11348 + (process as any).noAsar = true; + // Start listening + ioSession.listen(); - function parseStringArray(argName: string): readonly string[] { - const arg = findArgument(argName); - if (arg === undefined) { - return emptyArray; + if (Debug.isDebugging) { + Debug.enableDebugInfo(); } - return arg.split(",").filter(name => name !== ""); - } - const globalPlugins = parseStringArray("--globalPlugins"); - const pluginProbeLocations = parseStringArray("--pluginProbeLocations"); - const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads"); - - const useSingleInferredProject = hasArgument("--useSingleInferredProject"); - const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot"); - const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); - const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents"); - const syntaxOnly = hasArgument("--syntaxOnly"); - const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); - const noGetErrOnBackgroundUpdate = hasArgument("--noGetErrOnBackgroundUpdate"); - - logger.info(`Starting TS Server`); - logger.info(`Version: ${version}`); - logger.info(`Arguments: ${process.argv.join(" ")}`); - logger.info(`Platform: ${os.platform()} NodeVersion: ${nodeVersion} CaseSensitive: ${sys.useCaseSensitiveFileNames}`); - - const ioSession = new IOSession(); - process.on("uncaughtException", err => { - ioSession.logError(err, "unknown"); - }); - // See https://github.com/Microsoft/TypeScript/issues/11348 - (process as any).noAsar = true; - // Start listening - ioSession.listen(); - - if (Debug.isDebugging) { - Debug.enableDebugInfo(); - } + if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { + ts.sys.tryEnableSourceMapsForHost(); + } - if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { - ts.sys.tryEnableSourceMapsForHost(); + // Overwrites the current console messages to instead write to + // the log. This is so that language service plugins which use + // console.log don't break the message passing between tsserver + // and the client + console.log = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Info); + console.warn = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err); + console.error = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err); } - - // Overwrites the current console messages to instead write to - // the log. This is so that language service plugins which use - // console.log don't break the message passing between tsserver - // and the client - console.log = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Info); - console.warn = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err); - console.error = (...args) => logger.msg(args.length === 1 ? args[0] : args.join(", "), Msg.Err); -} +} \ No newline at end of file diff --git a/src/tsserver/tsconfig.json b/src/tsserver/tsconfig.json index 52f9fcc4d66f0..0e18ab053e0ed 100644 --- a/src/tsserver/tsconfig.json +++ b/src/tsserver/tsconfig.json @@ -1,6 +1,6 @@ { - "extends": "../tsconfig-noncomposite-base", - + "extends": "../tsconfig-library-base", + "compilerOptions": { "outFile": "../../built/local/tsserver.js", "types": [ From 709b9e2002af0aa77314b978777a88cf9b052392 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 26 Jun 2020 18:21:30 -0700 Subject: [PATCH 2/5] Use arrow functions --- src/tsserver/server.ts | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 1690fcd2ac960..bd9c21a3c4469 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -19,7 +19,7 @@ namespace ts.server { connect(options: { port: number }, onConnect?: () => void): NodeSocket } = require("net"); - const getGlobalTypingsCacheLocation = function () { + const getGlobalTypingsCacheLocation = () => { switch (process.platform) { case "win32": { const basePath = process.env.LOCALAPPDATA || @@ -42,9 +42,9 @@ namespace ts.server { default: return Debug.fail(`unsupported platform '${process.platform}'`); } - } + }; - const getNonWindowsCacheLocation = function (platformIsDarwin: boolean) { + const getNonWindowsCacheLocation = (platformIsDarwin: boolean) => { if (process.env.XDG_CACHE_HOME) { return process.env.XDG_CACHE_HOME; } @@ -57,7 +57,7 @@ namespace ts.server { ? "Library/Caches" : ".cache"; return combinePaths(normalizeSlashes(homePath), cacheFolder); - } + }; interface NodeChildProcess { send(message: any, sendHandle?: any): void; @@ -591,7 +591,7 @@ namespace ts.server { logToFile?: boolean; } - const parseLoggingEnvironmentString = function (logEnvStr: string | undefined): LogOptions { + const parseLoggingEnvironmentString = (logEnvStr: string | undefined): LogOptions => { if (!logEnvStr) { return {}; } @@ -636,9 +636,9 @@ namespace ts.server { } return { value: stripQuotes(pathStart), extraPartCounter }; } - } + }; - const getLogLevel = function (level: string | undefined) { + const getLogLevel = (level: string | undefined) => { if (level) { const l = level.toLowerCase(); for (const name in LogLevel) { @@ -648,10 +648,10 @@ namespace ts.server { } } return undefined; - } + }; // TSS_LOG "{ level: "normal | verbose | terse", file?: string}" - const createLogger = function () { + const createLogger = () => { const cmdLineLogFileName = findArgument("--logFile"); const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity")); const envLogOptions = parseLoggingEnvironmentString(process.env.TSS_LOG); @@ -668,13 +668,13 @@ namespace ts.server { const logVerbosity = cmdLineVerbosity || envLogOptions.detailLevel; return new Logger(substitutedLogFileName!, envLogOptions.traceToConsole!, logVerbosity!); // TODO: GH#18217 - } + }; // This places log file in the directory containing editorServices.js // TODO: check that this location is writable // average async stat takes about 30 microseconds // set chunk size to do 30 files in < 1 millisecond - const createPollingWatchedFileSet = function (interval = 2500, chunkSize = 30) { + const createPollingWatchedFileSet = (interval = 2500, chunkSize = 30) => { const watchedFiles: WatchedFile[] = []; let nextFileToCheck = 0; return { getModifiedTime, poll, startWatchTimer, addFile, removeFile }; @@ -750,7 +750,7 @@ namespace ts.server { function removeFile(file: WatchedFile) { unorderedRemoveItem(watchedFiles, file); } - } + }; // REVIEW: for now this implementation uses polling. // The advantage of polling is that it works reliably @@ -770,7 +770,7 @@ namespace ts.server { const pending: Buffer[] = []; let canWrite = true; - const writeMessage = function (buf: Buffer) { + const writeMessage = (buf: Buffer) => { if (!canWrite) { pending.push(buf); } @@ -778,16 +778,16 @@ namespace ts.server { canWrite = false; process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary); } - } + }; - const setCanWriteFlagAndWriteMessageIfNecessary = function () { + const setCanWriteFlagAndWriteMessageIfNecessary = () => { canWrite = true; if (pending.length) { writeMessage(pending.shift()!); } - } + }; - const extractWatchDirectoryCacheKey = function (path: string, currentDriveKey: string | undefined) { + const extractWatchDirectoryCacheKey = (path: string, currentDriveKey: string | undefined) => { path = normalizeSlashes(path); if (isUNCPath(path)) { // UNC path: extract server name @@ -811,11 +811,11 @@ namespace ts.server { } // do not cache any other cases return undefined; - } + }; - const isUNCPath = function (s: string): boolean { + const isUNCPath = (s: string): boolean => { return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash; - } + }; const logger = createLogger(); @@ -827,7 +827,7 @@ namespace ts.server { const noopWatcher: FileWatcher = { close: noop }; // This is the function that catches the exceptions when watching directory, and yet lets project service continue to function // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point - const watchDirectorySwallowingException = function (path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher { + const watchDirectorySwallowingException = (path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher => { try { return originalWatchDirectory(path, callback, recursive, options); } @@ -835,7 +835,7 @@ namespace ts.server { logger.info(`Exception when creating directory watcher: ${e.message}`); return noopWatcher; } - } + }; if (useWatchGuard) { const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined); @@ -923,10 +923,10 @@ namespace ts.server { cancellationToken = nullCancellationToken; } - const parseEventPort = function (eventPortStr: string | undefined) { + const parseEventPort = (eventPortStr: string | undefined) => { const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr); return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; - } + }; const eventPort: number | undefined = parseEventPort(findArgument("--eventPort")); const localeStr = findArgument("--locale"); @@ -941,13 +941,13 @@ namespace ts.server { const npmLocation = findArgument(Arguments.NpmLocation); const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation); - const parseStringArray = function (argName: string): readonly string[] { + const parseStringArray = (argName: string): readonly string[] => { const arg = findArgument(argName); if (arg === undefined) { return emptyArray; } return arg.split(",").filter(name => name !== ""); - } + }; const globalPlugins = parseStringArray("--globalPlugins"); const pluginProbeLocations = parseStringArray("--pluginProbeLocations"); From 18b4de5ff9faa396cc5df78ff4e03a59ad0206b7 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 29 Jun 2020 13:52:27 -0700 Subject: [PATCH 3/5] Subsume tsserverlibrary.* --- .github/workflows/new-release-branch.yaml | 4 +- .github/workflows/set-version.yaml | 4 +- Gulpfile.js | 132 +++++++----------- scripts/configureLanguageServiceBuild.ts | 3 +- scripts/produceLKG.ts | 5 +- src/testRunner/unittests/publicApi.ts | 2 +- src/tsserver/tsconfig.json | 4 +- src/tsserverlibrary/tsconfig.json | 17 --- src/tsserverlibrary/tsserverlibrary.ts | 0 .../{tsserverlibrary.d.ts => tsserver.d.ts} | 0 10 files changed, 63 insertions(+), 108 deletions(-) delete mode 100644 src/tsserverlibrary/tsconfig.json delete mode 100644 src/tsserverlibrary/tsserverlibrary.ts rename tests/baselines/reference/api/{tsserverlibrary.d.ts => tsserver.d.ts} (100%) diff --git a/.github/workflows/new-release-branch.yaml b/.github/workflows/new-release-branch.yaml index 1e145a0932f79..fc2e6d0869282 100644 --- a/.github/workflows/new-release-branch.yaml +++ b/.github/workflows/new-release-branch.yaml @@ -21,7 +21,7 @@ jobs: sed -i -e 's/"version": ".*"/"version": "${{ github.event.client_payload.package_version }}"/g' package.json sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' src/compiler/corePublic.ts sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/typescript.d.ts - sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts + sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserver.d.ts sed -i -e 's/const version = `${versionMajorMinor}.0-.*`/const version = `${versionMajorMinor}.0-${{ github.event.client_payload.core_tag || 'dev' }}`/g' src/compiler/corePublic.ts npm install gulp LKG @@ -30,7 +30,7 @@ jobs: git add package.json git add src/compiler/corePublic.ts git add tests/baselines/reference/api/typescript.d.ts - git add tests/baselines/reference/api/tsserverlibrary.d.ts + git add tests/baselines/reference/api/tsserver.d.ts git add ./lib git config user.email "ts_bot@rcavanaugh.com" git config user.name "TypeScript Bot" diff --git a/.github/workflows/set-version.yaml b/.github/workflows/set-version.yaml index a31d849cb4131..78ad4668219e3 100644 --- a/.github/workflows/set-version.yaml +++ b/.github/workflows/set-version.yaml @@ -27,7 +27,7 @@ jobs: sed -i -e 's/"version": ".*"/"version": "${{ github.event.client_payload.package_version }}"/g' package.json sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' src/compiler/corePublic.ts sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/typescript.d.ts - sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts + sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserver.d.ts sed -i -e 's/const version = .*;/const version = "${{ github.event.client_payload.package_version }}" as string;/g' src/compiler/corePublic.ts npm install gulp LKG @@ -36,7 +36,7 @@ jobs: git add package.json git add src/compiler/corePublic.ts git add tests/baselines/reference/api/typescript.d.ts - git add tests/baselines/reference/api/tsserverlibrary.d.ts + git add tests/baselines/reference/api/tsserver.d.ts git add ./lib git config user.email "ts_bot@rcavanaugh.com" git config user.name "TypeScript Bot" diff --git a/Gulpfile.js b/Gulpfile.js index 095ea5025c4d0..7bd65e61ff51d 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -215,14 +215,55 @@ task("watch-services").flags = { " --built": "Compile using the built version of the compiler." }; -const buildServer = () => buildProject("src/tsserver", cmdLineOptions); +const buildServer = (() => { + // build tsserver.out.js + const buildServerOut = () => buildProject("src/tsserver/tsconfig.json", cmdLineOptions); + + // create tsserver.js + const createServerJs = () => src("built/local/tsserver.out.js") + .pipe(newer("built/local/tsserver.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(prependFile(copyright)) + .pipe(rename("tsserver.js")) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); + + // create tsserver.d.ts + const createServerDts = () => src("built/local/tsserver.out.d.ts") + .pipe(newer("built/local/tsserver.d.ts")) + .pipe(prependFile(copyright)) + .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) + .pipe(append("\nexport = ts;\nexport as namespace ts;")) + .pipe(rename("tsserver.d.ts")) + .pipe(dest("built/local")); + + return series( + buildServerOut, + createServerJs, + createServerDts, + ); +})(); task("tsserver", series(preBuild, buildServer)); task("tsserver").description = "Builds the language server"; task("tsserver").flags = { " --built": "Compile using the built version of the compiler." }; -const cleanServer = () => cleanProject("src/tsserver"); +const cleanServer = async () => { + await cleanProject("src/tsserver"); + if (fs.existsSync("built/local/tsserver.tsconfig.json")) { + await cleanProject("built/local/tsserver.tsconfig.json"); + } + await del([ + "built/local/tsserver.out.js", + "built/local/tsserver.out.js.map", + "built/local/tsserver.out.d.ts", + "built/local/tsserver.out.tsbuildinfo", + "built/local/tsserver.js", + "built/local/tsserver.js.map", + "built/local/tsserver.d.ts", + ]); +}; cleanTasks.push(cleanServer); task("clean-tsserver", cleanServer); task("clean-tsserver").description = "Cleans outputs for the language server"; @@ -249,76 +290,8 @@ task("watch-min").flags = { " --built": "Compile using the built version of the compiler." }; -const buildLssl = (() => { - // build tsserverlibrary.out.js - const buildServerLibraryOut = () => buildProject("src/tsserverlibrary/tsconfig.json", cmdLineOptions); - - // create tsserverlibrary.js - const createServerLibraryJs = () => src("built/local/tsserverlibrary.out.js") - .pipe(newer("built/local/tsserverlibrary.js")) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(prependFile(copyright)) - .pipe(rename("tsserverlibrary.js")) - .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) - .pipe(dest("built/local")); - - // create tsserverlibrary.d.ts - const createServerLibraryDts = () => src("built/local/tsserverlibrary.out.d.ts") - .pipe(newer("built/local/tsserverlibrary.d.ts")) - .pipe(prependFile(copyright)) - .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) - .pipe(append("\nexport = ts;\nexport as namespace ts;")) - .pipe(rename("tsserverlibrary.d.ts")) - .pipe(dest("built/local")); - - return series( - buildServerLibraryOut, - createServerLibraryJs, - createServerLibraryDts, - ); -})(); -task("lssl", series(preBuild, buildLssl)); -task("lssl").description = "Builds language service server library"; -task("lssl").flags = { - " --built": "Compile using the built version of the compiler." -}; - -const cleanLssl = async () => { - if (fs.existsSync("built/local/tsserverlibrary.tsconfig.json")) { - await cleanProject("built/local/tsserverlibrary.tsconfig.json"); - } - await del([ - "built/local/tsserverlibrary.out.js", - "built/local/tsserverlibrary.out.d.ts", - "built/local/tsserverlibrary.out.tsbuildinfo", - "built/local/tsserverlibrary.js", - "built/local/tsserverlibrary.d.ts", - ]); -}; -cleanTasks.push(cleanLssl); -task("clean-lssl", cleanLssl); -task("clean-lssl").description = "Clean outputs for the language service server library"; - -const watchLssl = () => watch([ - "src/compiler/tsconfig.json", - "src/compiler/**/*.ts", - "src/jsTyping/tsconfig.json", - "src/jsTyping/**/*.ts", - "src/services/tsconfig.json", - "src/services/**/*.ts", - "src/server/tsconfig.json", - "src/server/**/*.ts", - "src/tsserver/tsconfig.json", - "src/tsserver/**/*.ts", -], buildLssl); -task("watch-lssl", series(preBuild, parallel(watchLib, watchDiagnostics, watchLssl))); -task("watch-lssl").description = "Watch for changes and rebuild tsserverlibrary only"; -task("watch-lssl").flags = { - " --built": "Compile using the built version of the compiler." -}; - const buildTests = () => buildProject("src/testRunner"); -task("tests", series(preBuild, parallel(buildLssl, buildTests))); +task("tests", series(preBuild, parallel(buildServer, buildTests))); task("tests").description = "Builds the test infrastructure"; task("tests").flags = { " --built": "Compile using the built version of the compiler." @@ -412,7 +385,7 @@ const cleanTypesMap = () => del("built/local/typesMap.json"); cleanTasks.push(cleanTypesMap); // Drop a copy of diagnosticMessages.generated.json into the built/local folder. This allows -// it to be synced to the Azure DevOps repo, so that it can get picked up by the build +// it to be synced to the Azure DevOps repo, so that it can get picked up by the build // pipeline that generates the localization artifacts that are then fed into the translation process. const builtLocalDiagnosticMessagesGeneratedJson = "built/local/diagnosticMessages.generated.json"; const copyBuiltLocalDiagnosticMessages = () => src(diagnosticMessagesGeneratedJson) @@ -428,13 +401,13 @@ task("other-outputs").description = "Builds miscelaneous scripts and documents d const buildFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("build")); }; const buildFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("build")); }; -task("local", series(buildFoldStart, preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs), buildFoldEnd)); +task("local", series(buildFoldStart, preBuild, parallel(localize, buildTsc, buildServer, buildServices, buildOtherOutputs), buildFoldEnd)); task("local").description = "Builds the full compiler and services"; task("local").flags = { " --built": "Compile using the built version of the compiler." }; -task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl))); +task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer))); task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests)."; task("watch-local").flags = { " --built": "Compile using the built version of the compiler." @@ -444,7 +417,7 @@ const generateCodeCoverage = () => exec("istanbul", ["cover", "node_modules/moch task("generate-code-coverage", series(preBuild, buildTests, generateCodeCoverage)); task("generate-code-coverage").description = "Generates code coverage data via istanbul"; -const preTest = parallel(buildTsc, buildTests, buildServices, buildLssl); +const preTest = parallel(buildTsc, buildTests, buildServices, buildServer); preTest.displayName = "preTest"; const postTest = (done) => cmdLineOptions.lint ? lint(done) : done(); @@ -529,7 +502,7 @@ const cleanInstrumenter = () => cleanProject("src/instrumenter"); cleanTasks.push(cleanInstrumenter); const tscInstrumented = () => exec(process.execPath, ["built/local/instrumenter.js", "record", cmdLineOptions.tests || "iocapture", "built/local"]); -task("tsc-instrumented", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildLoggedIO, buildInstrumenter), tscInstrumented)); +task("tsc-instrumented", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLoggedIO, buildInstrumenter), tscInstrumented)); task("tsc-instrumented").description = "Builds an instrumented tsc.js"; task("tsc-instrumented").flags = { "-t --tests=": "The test to run." @@ -563,10 +536,9 @@ const produceLKG = async () => { "built/local/typescriptServices.js", "built/local/typescriptServices.d.ts", "built/local/tsserver.js", + "built/local/tsserver.d.ts", "built/local/typescript.js", "built/local/typescript.d.ts", - "built/local/tsserverlibrary.js", - "built/local/tsserverlibrary.d.ts", "built/local/typingsInstaller.js", "built/local/cancellationToken.js" ].concat(libs.map(lib => lib.target)); @@ -584,7 +556,7 @@ const produceLKG = async () => { } }; -task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildOtherOutputs, buildReleaseTsc), produceLKG)); +task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildOtherOutputs, buildReleaseTsc), produceLKG)); task("LKG").description = "Makes a new LKG out of the built js files"; task("LKG").flags = { " --built": "Compile using the built version of the compiler.", @@ -630,7 +602,7 @@ const watchRuntests = () => watch(["built/local/*.js", "tests/cases/**/*.ts", "t await runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ true); } }); -task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchLssl, watchTests, watchRuntests))); +task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchServer, watchTests, watchRuntests))); task("watch").description = "Watches for changes and rebuilds and runs tests in parallel."; task("watch").flags = { "-t --tests=": "Pattern for tests to run. Forces tests to be run in a single worker.", diff --git a/scripts/configureLanguageServiceBuild.ts b/scripts/configureLanguageServiceBuild.ts index d61aeaeec35f3..403221865368a 100644 --- a/scripts/configureLanguageServiceBuild.ts +++ b/scripts/configureLanguageServiceBuild.ts @@ -45,13 +45,12 @@ function main(): void { const toRemove = [ // JS Files "tsserver.js", - "tsserverlibrary.js", "typescriptServices.js", "typingsInstaller.js", "tsc.js", // DTS files "typescriptServices.d.ts", - "tsserverlibrary.d.ts" + "tsserver.d.ts" ]; // Get a link to the main dependency JS file diff --git a/scripts/produceLKG.ts b/scripts/produceLKG.ts index 762290bcfa6a0..6ca438a9c627e 100644 --- a/scripts/produceLKG.ts +++ b/scripts/produceLKG.ts @@ -57,8 +57,7 @@ async function buildProtocol() { async function copyScriptOutputs() { await copyWithCopyright("cancellationToken.js"); await copyWithCopyright("tsc.release.js", "tsc.js"); - await copyWithCopyright("tsserver.js"); - await copyFromBuiltLocal("tsserverlibrary.js"); // copyright added by build + await copyFromBuiltLocal("tsserver.js"); // copyright added by build await copyFromBuiltLocal("typescript.js"); // copyright added by build await copyFromBuiltLocal("typescriptServices.js"); // copyright added by build await copyWithCopyright("typingsInstaller.js"); @@ -66,7 +65,7 @@ async function copyScriptOutputs() { } async function copyDeclarationOutputs() { - await copyFromBuiltLocal("tsserverlibrary.d.ts"); // copyright added by build + await copyFromBuiltLocal("tsserver.d.ts"); // copyright added by build await copyFromBuiltLocal("typescript.d.ts"); // copyright added by build await copyFromBuiltLocal("typescriptServices.d.ts"); // copyright added by build } diff --git a/src/testRunner/unittests/publicApi.ts b/src/testRunner/unittests/publicApi.ts index 80df40e6d9b4e..4661aaf837b65 100644 --- a/src/testRunner/unittests/publicApi.ts +++ b/src/testRunner/unittests/publicApi.ts @@ -28,7 +28,7 @@ describe("unittests:: Public APIs", () => { }); describe("for the language server", () => { - verifyApi("tsserverlibrary.d.ts"); + verifyApi("tsserver.d.ts"); }); }); diff --git a/src/tsserver/tsconfig.json b/src/tsserver/tsconfig.json index 0e18ab053e0ed..31f49a544a0bb 100644 --- a/src/tsserver/tsconfig.json +++ b/src/tsserver/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../tsconfig-library-base", "compilerOptions": { - "outFile": "../../built/local/tsserver.js", + "outFile": "../../built/local/tsserver.out.js", + "sourceMap": true, "types": [ "node" ] @@ -11,6 +12,7 @@ "server.ts" ], "references": [ + { "path": "../shims", "prepend": true }, { "path": "../compiler", "prepend": true }, { "path": "../services", "prepend": true }, { "path": "../jsTyping", "prepend": true }, diff --git a/src/tsserverlibrary/tsconfig.json b/src/tsserverlibrary/tsconfig.json deleted file mode 100644 index b9012d8d6fc47..0000000000000 --- a/src/tsserverlibrary/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../tsconfig-library-base", - "compilerOptions": { - "outFile": "../../built/local/tsserverlibrary.out.js" - }, - "files": [ - "tsserverlibrary.ts" - ], - "references": [ - { "path": "../shims", "prepend": true }, - { "path": "../compiler", "prepend": true }, - { "path": "../jsTyping", "prepend": true }, - { "path": "../services", "prepend": true }, - { "path": "../server", "prepend": true }, - { "path": "../compat", "prepend": true } - ] -} diff --git a/src/tsserverlibrary/tsserverlibrary.ts b/src/tsserverlibrary/tsserverlibrary.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserver.d.ts similarity index 100% rename from tests/baselines/reference/api/tsserverlibrary.d.ts rename to tests/baselines/reference/api/tsserver.d.ts From 406ce516ce5f3e161f666396828aefb85c9bc9d6 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 29 Jun 2020 14:36:30 -0700 Subject: [PATCH 4/5] Update API baseline --- tests/baselines/reference/api/tsserver.d.ts | 152 ++++++++++---------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/tests/baselines/reference/api/tsserver.d.ts b/tests/baselines/reference/api/tsserver.d.ts index e19dcd382c20d..f4f148a7788da 100644 --- a/tests/baselines/reference/api/tsserver.d.ts +++ b/tests/baselines/reference/api/tsserver.d.ts @@ -5102,81 +5102,6 @@ declare namespace ts { } type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; } -declare namespace ts.server { - type ActionSet = "action::set"; - type ActionInvalidate = "action::invalidate"; - type ActionPackageInstalled = "action::packageInstalled"; - type EventTypesRegistry = "event::typesRegistry"; - type EventBeginInstallTypes = "event::beginInstallTypes"; - type EventEndInstallTypes = "event::endInstallTypes"; - type EventInitializationFailed = "event::initializationFailed"; -} -declare namespace ts.server { - interface TypingInstallerResponse { - readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; - } - interface TypingInstallerRequestWithProjectName { - readonly projectName: string; - } - interface DiscoverTypings extends TypingInstallerRequestWithProjectName { - readonly fileNames: string[]; - readonly projectRootPath: Path; - readonly compilerOptions: CompilerOptions; - readonly watchOptions?: WatchOptions; - readonly typeAcquisition: TypeAcquisition; - readonly unresolvedImports: SortedReadonlyArray; - readonly cachePath?: string; - readonly kind: "discover"; - } - interface CloseProject extends TypingInstallerRequestWithProjectName { - readonly kind: "closeProject"; - } - interface TypesRegistryRequest { - readonly kind: "typesRegistry"; - } - interface InstallPackageRequest extends TypingInstallerRequestWithProjectName { - readonly kind: "installPackage"; - readonly fileName: Path; - readonly packageName: string; - readonly projectRootPath: Path; - } - interface PackageInstalledResponse extends ProjectResponse { - readonly kind: ActionPackageInstalled; - readonly success: boolean; - readonly message: string; - } - interface InitializationFailedResponse extends TypingInstallerResponse { - readonly kind: EventInitializationFailed; - readonly message: string; - readonly stack?: string; - } - interface ProjectResponse extends TypingInstallerResponse { - readonly projectName: string; - } - interface InvalidateCachedTypings extends ProjectResponse { - readonly kind: ActionInvalidate; - } - interface InstallTypes extends ProjectResponse { - readonly kind: EventBeginInstallTypes | EventEndInstallTypes; - readonly eventId: number; - readonly typingsInstallerVersion: string; - readonly packagesToInstall: readonly string[]; - } - interface BeginInstallTypes extends InstallTypes { - readonly kind: EventBeginInstallTypes; - } - interface EndInstallTypes extends InstallTypes { - readonly kind: EventEndInstallTypes; - readonly installSuccess: boolean; - } - interface SetTypings extends ProjectResponse { - readonly typeAcquisition: TypeAcquisition; - readonly compilerOptions: CompilerOptions; - readonly typings: string[]; - readonly unresolvedImports: SortedReadonlyArray; - readonly kind: ActionSet; - } -} declare namespace ts { interface Node { getSourceFile(): SourceFile; @@ -6325,6 +6250,81 @@ declare namespace ts { */ function transform(source: T | T[], transformers: TransformerFactory[], compilerOptions?: CompilerOptions): TransformationResult; } +declare namespace ts.server { + type ActionSet = "action::set"; + type ActionInvalidate = "action::invalidate"; + type ActionPackageInstalled = "action::packageInstalled"; + type EventTypesRegistry = "event::typesRegistry"; + type EventBeginInstallTypes = "event::beginInstallTypes"; + type EventEndInstallTypes = "event::endInstallTypes"; + type EventInitializationFailed = "event::initializationFailed"; +} +declare namespace ts.server { + interface TypingInstallerResponse { + readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed; + } + interface TypingInstallerRequestWithProjectName { + readonly projectName: string; + } + interface DiscoverTypings extends TypingInstallerRequestWithProjectName { + readonly fileNames: string[]; + readonly projectRootPath: Path; + readonly compilerOptions: CompilerOptions; + readonly watchOptions?: WatchOptions; + readonly typeAcquisition: TypeAcquisition; + readonly unresolvedImports: SortedReadonlyArray; + readonly cachePath?: string; + readonly kind: "discover"; + } + interface CloseProject extends TypingInstallerRequestWithProjectName { + readonly kind: "closeProject"; + } + interface TypesRegistryRequest { + readonly kind: "typesRegistry"; + } + interface InstallPackageRequest extends TypingInstallerRequestWithProjectName { + readonly kind: "installPackage"; + readonly fileName: Path; + readonly packageName: string; + readonly projectRootPath: Path; + } + interface PackageInstalledResponse extends ProjectResponse { + readonly kind: ActionPackageInstalled; + readonly success: boolean; + readonly message: string; + } + interface InitializationFailedResponse extends TypingInstallerResponse { + readonly kind: EventInitializationFailed; + readonly message: string; + readonly stack?: string; + } + interface ProjectResponse extends TypingInstallerResponse { + readonly projectName: string; + } + interface InvalidateCachedTypings extends ProjectResponse { + readonly kind: ActionInvalidate; + } + interface InstallTypes extends ProjectResponse { + readonly kind: EventBeginInstallTypes | EventEndInstallTypes; + readonly eventId: number; + readonly typingsInstallerVersion: string; + readonly packagesToInstall: readonly string[]; + } + interface BeginInstallTypes extends InstallTypes { + readonly kind: EventBeginInstallTypes; + } + interface EndInstallTypes extends InstallTypes { + readonly kind: EventEndInstallTypes; + readonly installSuccess: boolean; + } + interface SetTypings extends ProjectResponse { + readonly typeAcquisition: TypeAcquisition; + readonly compilerOptions: CompilerOptions; + readonly typings: string[]; + readonly unresolvedImports: SortedReadonlyArray; + readonly kind: ActionSet; + } +} declare namespace ts.server { interface CompressedData { length: number; @@ -10661,6 +10661,8 @@ declare namespace ts { /** @deprecated Use `isTypeAssertionExpression` instead. */ const isTypeAssertion: (node: Node) => node is TypeAssertion; } +declare namespace ts.server { +} export = ts; export as namespace ts; \ No newline at end of file From c7bc0555a18344ff7b1b6ee0bdb59f8102d10cef Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 29 Jun 2020 16:01:56 -0700 Subject: [PATCH 5/5] Drop invocation of removed gulp task --- scripts/build/tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/tests.js b/scripts/build/tests.js index 5a6297768ee57..c1a1254930299 100644 --- a/scripts/build/tests.js +++ b/scripts/build/tests.js @@ -135,7 +135,7 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, // finally, do a sanity check and build the compiler with the built version of itself log.info("Starting sanity check build..."); // Cleanup everything except lint rules (we'll need those later and would rather not waste time rebuilding them) - await exec("gulp", ["clean-tsc", "clean-services", "clean-tsserver", "clean-lssl", "clean-tests"], { cancelToken }); + await exec("gulp", ["clean-tsc", "clean-services", "clean-tsserver", "clean-tests"], { cancelToken }); const { exitCode } = await exec("gulp", ["local", "--lkg=false"], { cancelToken }); if (exitCode !== 0) { errorStatus = exitCode;