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/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; 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/server.ts b/src/tsserver/server.ts index 43961eb0ba597..bd9c21a3c4469 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 = () => { + 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}'`); } - } + }; - static padStringRight(str: string, padding: string) { - return (str + padding).slice(0, padding.length); - } + const 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); + }; - close() { - if (this.fd >= 0) { - fs.close(this.fd, noop); + 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 + } + } } - } - - 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; - } + static padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); + } - 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; + 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)); - } - }; - - 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 = (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 = (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}" + const 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}" - function createLogger() { - 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()) + 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 - 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; - } + // average async stat takes about 30 microseconds + // set chunk size to do 30 files in < 1 millisecond + const createPollingWatchedFileSet = (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 - }; - - watchedFiles.push(file); - if (watchedFiles.length === 1) { - startWatchTimer(); + return file; } - return file; - } - function removeFile(file: WatchedFile) { - unorderedRemoveItem(watchedFiles, 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(); - - const pending: Buffer[] = []; - let canWrite = true; - - function writeMessage(buf: Buffer) { - if (!canWrite) { - pending.push(buf); - } - else { - canWrite = false; - process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary); - } - } + // 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; + + const writeMessage = (buf: Buffer) => { + if (!canWrite) { + pending.push(buf); + } + else { + canWrite = false; + process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary); + } + }; - function setCanWriteFlagAndWriteMessageIfNecessary() { - canWrite = true; - if (pending.length) { - writeMessage(pending.shift()!); - } - } + const setCanWriteFlagAndWriteMessageIfNecessary = () => { + canWrite = true; + if (pending.length) { + writeMessage(pending.shift()!); + } + }; - 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; - } - 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; - } + const 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; + } + 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; + }; - function isUNCPath(s: string): boolean { - return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash; - } + const isUNCPath = (s: string): boolean => { + return s.length > 2 && s.charCodeAt(0) === CharacterCodes.slash && s.charCodeAt(1) === CharacterCodes.slash; + }; - 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 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 + const 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; + } + }; - 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 => { + sys.require = (initialDir: string, moduleName: string): RequireResult => { + try { + return { module: require(resolveJSModule(moduleName, initialDir, sys)), error: undefined }; + } + catch (error) { + return { module: undefined, error }; + } + }; + + 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; - } - - 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 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")); - setStackTraceLimit(); + const localeStr = findArgument("--locale"); + if (localeStr) { + validateLocaleAndSetLanguage(localeStr, sys); + } - 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(); - function parseStringArray(argName: string): readonly string[] { - const arg = findArgument(argName); - if (arg === undefined) { - return emptyArray; - } - return arg.split(",").filter(name => name !== ""); - } + 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 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(); - } + const parseStringArray = (argName: string): readonly string[] => { + const arg = findArgument(argName); + if (arg === undefined) { + return emptyArray; + } + return arg.split(",").filter(name => name !== ""); + }; - if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) { - ts.sys.tryEnableSourceMapsForHost(); + 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(); + } + + // 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..31f49a544a0bb 100644 --- a/src/tsserver/tsconfig.json +++ b/src/tsserver/tsconfig.json @@ -1,8 +1,9 @@ { - "extends": "../tsconfig-noncomposite-base", - + "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 99% rename from tests/baselines/reference/api/tsserverlibrary.d.ts rename to tests/baselines/reference/api/tsserver.d.ts index e19dcd382c20d..f4f148a7788da 100644 --- a/tests/baselines/reference/api/tsserverlibrary.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