From 45234f59adeae209fa891e47358c408242ffaa73 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 30 Nov 2022 10:35:51 -0800 Subject: [PATCH 1/5] Remove webServer First draft; I may move some things around to be more readable. --- src/testRunner/_namespaces/ts.server.ts | 1 - src/testRunner/_namespaces/ts.ts | 1 - src/testRunner/tests.ts | 1 - src/testRunner/tsconfig.json | 1 - .../unittests/tsserver/webServer.ts | 372 ------------------ src/tsconfig.json | 1 - src/tsserver/_namespaces/ts.server.ts | 2 - src/tsserver/_namespaces/ts.ts | 1 - src/tsserver/nodeServer.ts | 86 +++- src/tsserver/server.ts | 17 +- src/tsserver/tsconfig.json | 1 - src/tsserver/webServer.ts | 163 -------- src/webServer/_namespaces/ts.server.ts | 5 - src/webServer/_namespaces/ts.ts | 8 - src/webServer/tsconfig.json | 15 - src/webServer/webServer.ts | 279 ------------- 16 files changed, 84 insertions(+), 870 deletions(-) delete mode 100644 src/testRunner/unittests/tsserver/webServer.ts delete mode 100644 src/tsserver/webServer.ts delete mode 100644 src/webServer/_namespaces/ts.server.ts delete mode 100644 src/webServer/_namespaces/ts.ts delete mode 100644 src/webServer/tsconfig.json delete mode 100644 src/webServer/webServer.ts diff --git a/src/testRunner/_namespaces/ts.server.ts b/src/testRunner/_namespaces/ts.server.ts index 225d9d4e6aa24..3af988500c03b 100644 --- a/src/testRunner/_namespaces/ts.server.ts +++ b/src/testRunner/_namespaces/ts.server.ts @@ -2,7 +2,6 @@ export * from "../../jsTyping/_namespaces/ts.server"; export * from "../../server/_namespaces/ts.server"; -export * from "../../webServer/_namespaces/ts.server"; export * from "../../typingsInstallerCore/_namespaces/ts.server"; export * from "../../harness/_namespaces/ts.server"; export * from "../../loggedIO/_namespaces/ts.server"; diff --git a/src/testRunner/_namespaces/ts.ts b/src/testRunner/_namespaces/ts.ts index 14184486f6fca..d9f1d2e9eef1e 100644 --- a/src/testRunner/_namespaces/ts.ts +++ b/src/testRunner/_namespaces/ts.ts @@ -5,7 +5,6 @@ export * from "../../executeCommandLine/_namespaces/ts"; export * from "../../services/_namespaces/ts"; export * from "../../jsTyping/_namespaces/ts"; export * from "../../server/_namespaces/ts"; -export * from "../../webServer/_namespaces/ts"; export * from "../../typingsInstallerCore/_namespaces/ts"; export * from "../../deprecatedCompat/_namespaces/ts"; export * from "../../harness/_namespaces/ts"; diff --git a/src/testRunner/tests.ts b/src/testRunner/tests.ts index 8f0af1d9927db..c4a34d424b222 100644 --- a/src/testRunner/tests.ts +++ b/src/testRunner/tests.ts @@ -185,5 +185,4 @@ import "./unittests/tsserver/typeReferenceDirectives"; import "./unittests/tsserver/typingsInstaller"; import "./unittests/tsserver/versionCache"; import "./unittests/tsserver/watchEnvironment"; -import "./unittests/tsserver/webServer"; import "./unittests/debugDeprecation"; diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index abd3964286b73..d1c9eb3671e94 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -11,7 +11,6 @@ { "path": "../services" }, { "path": "../jsTyping" }, { "path": "../server" }, - { "path": "../webServer" }, { "path": "../typingsInstallerCore" }, { "path": "../deprecatedCompat" }, { "path": "../harness" }, diff --git a/src/testRunner/unittests/tsserver/webServer.ts b/src/testRunner/unittests/tsserver/webServer.ts deleted file mode 100644 index 25de36c1ff4e6..0000000000000 --- a/src/testRunner/unittests/tsserver/webServer.ts +++ /dev/null @@ -1,372 +0,0 @@ -import * as ts from "../../_namespaces/ts"; -import * as Utils from "../../_namespaces/Utils"; -import { - createServerHost, - File, - libFile, -} from "../virtualFileSystemWithWatch"; -import { - checkNumberOfProjects, - checkProjectActualFiles, - nullLogger, - protocolFileLocationFromSubstring, - protocolTextSpanWithContextFromSubstring, -} from "./helpers"; - -/* eslint-disable local/boolean-trivia */ -describe("unittests:: tsserver:: webServer", () => { - class TestWorkerSession extends ts.server.WorkerSession { - constructor(host: ts.server.ServerHost, webHost: ts.server.HostWithWriteMessage, options: Partial, logger: ts.server.Logger) { - super( - host, - webHost, - { - globalPlugins: undefined, - pluginProbeLocations: undefined, - allowLocalPluginLoads: undefined, - useSingleInferredProject: true, - useInferredProjectPerProjectRoot: false, - suppressDiagnosticEvents: false, - noGetErrOnBackgroundUpdate: true, - syntaxOnly: undefined, - serverMode: undefined, - ...options - }, - logger, - ts.server.nullCancellationToken, - () => ts.emptyArray - ); - } - - getProjectService() { - return this.projectService; - } - } - - function setup(logLevel: ts.server.LogLevel | undefined, options?: Partial, importPlugin?: ts.server.ServerHost["importPlugin"]) { - const host = createServerHost([libFile], { windowsStyleRoot: "c:/" }); - const messages: any[] = []; - const webHost: ts.server.WebHost = { - readFile: s => host.readFile(s), - fileExists: s => host.fileExists(s), - writeMessage: s => messages.push(s), - }; - const webSys = ts.server.createWebSystem(webHost, ts.emptyArray, () => host.getExecutingFilePath()); - webSys.importPlugin = importPlugin; - const logger = logLevel !== undefined ? new ts.server.MainProcessLogger(logLevel, webHost) : nullLogger(); - const session = new TestWorkerSession(webSys, webHost, { serverMode: ts.LanguageServiceMode.PartialSemantic, ...options }, logger); - return { getMessages: () => messages, clearMessages: () => messages.length = 0, session }; - - } - - describe("open files are added to inferred project and semantic operations succeed", () => { - function verify(logLevel: ts.server.LogLevel | undefined) { - const { session, clearMessages, getMessages } = setup(logLevel); - const service = session.getProjectService(); - const file: File = { - path: "^memfs:/sample-folder/large.ts", - content: "export const numberConst = 10; export const arrayConst: Array = [];" - }; - session.executeCommand({ - seq: 1, - type: "request", - command: ts.server.protocol.CommandTypes.Open, - arguments: { - file: file.path, - fileContent: file.content - } - }); - checkNumberOfProjects(service, { inferredProjects: 1 }); - const project = service.inferredProjects[0]; - checkProjectActualFiles(project, ["/lib.d.ts", file.path]); // Lib files are rooted - verifyQuickInfo(); - verifyGotoDefInLib(); - - function verifyQuickInfo() { - clearMessages(); - const start = protocolFileLocationFromSubstring(file, "numberConst"); - session.onMessage({ - seq: 2, - type: "request", - command: ts.server.protocol.CommandTypes.Quickinfo, - arguments: start - }); - assert.deepEqual(ts.last(getMessages()), { - seq: 0, - type: "response", - command: ts.server.protocol.CommandTypes.Quickinfo, - request_seq: 2, - success: true, - performanceData: undefined, - body: { - kind: ts.ScriptElementKind.constElement, - kindModifiers: "export", - start: { line: start.line, offset: start.offset }, - end: { line: start.line, offset: start.offset + "numberConst".length }, - displayString: "const numberConst: 10", - documentation: "", - tags: [] - } - }); - verifyLogger(); - } - - function verifyGotoDefInLib() { - clearMessages(); - const start = protocolFileLocationFromSubstring(file, "Array"); - session.onMessage({ - seq: 3, - type: "request", - command: ts.server.protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: start - }); - assert.deepEqual(ts.last(getMessages()), { - seq: 0, - type: "response", - command: ts.server.protocol.CommandTypes.DefinitionAndBoundSpan, - request_seq: 3, - success: true, - performanceData: undefined, - body: { - definitions: [{ - file: "/lib.d.ts", - ...protocolTextSpanWithContextFromSubstring({ - fileText: libFile.content, - text: "Array", - contextText: "interface Array { length: number; [n: number]: T; }" - }) - }], - textSpan: { - start: { line: start.line, offset: start.offset }, - end: { line: start.line, offset: start.offset + "Array".length }, - } - } - }); - verifyLogger(); - } - - function verifyLogger() { - const messages = getMessages(); - assert.equal(messages.length, logLevel === ts.server.LogLevel.verbose ? 4 : 1, `Expected ${JSON.stringify(messages)}`); - if (logLevel === ts.server.LogLevel.verbose) { - verifyLogMessages(messages[0], "info"); - verifyLogMessages(messages[1], "perf"); - verifyLogMessages(messages[2], "info"); - } - clearMessages(); - } - - function verifyLogMessages(actual: any, expectedLevel: ts.server.MessageLogLevel) { - assert.equal(actual.type, "log"); - assert.equal(actual.level, expectedLevel); - } - } - - it("with logging enabled", () => { - verify(ts.server.LogLevel.verbose); - }); - - it("with logging disabled", () => { - verify(/*logLevel*/ undefined); - }); - }); - - describe("async loaded plugins", () => { - it("plugins are not loaded immediately", async () => { - let pluginModuleInstantiated = false; - let pluginInvoked = false; - const importPlugin = async (_root: string, _moduleName: string): Promise => { - await Promise.resolve(); // simulate at least a single turn delay - pluginModuleInstantiated = true; - return { - module: (() => { - pluginInvoked = true; - return { create: info => info.languageService }; - }) as ts.server.PluginModuleFactory, - error: undefined - }; - }; - - const { session } = setup(/*logLevel*/ undefined, { globalPlugins: ["plugin-a"] }, importPlugin); - const projectService = session.getProjectService(); - - session.executeCommand({ seq: 1, type: "request", command: ts.server.protocol.CommandTypes.Open, arguments: { file: "^memfs:/foo.ts", content: "" } }); - - // This should be false because `executeCommand` should have already triggered - // plugin enablement asynchronously and there are no plugin enablements currently - // being processed. - expect(projectService.hasNewPluginEnablementRequests()).eq(false); - - // Should be true because async imports have already been triggered in the background - expect(projectService.hasPendingPluginEnablements()).eq(true); - - // Should be false because resolution of async imports happens in a later turn. - expect(pluginModuleInstantiated).eq(false); - - await projectService.waitForPendingPlugins(); - - // at this point all plugin modules should have been instantiated and all plugins - // should have been invoked - expect(pluginModuleInstantiated).eq(true); - expect(pluginInvoked).eq(true); - }); - - it("plugins evaluation in correct order even if imports resolve out of order", async () => { - const pluginADeferred = Utils.defer(); - const pluginBDeferred = Utils.defer(); - const log: string[] = []; - const importPlugin = async (_root: string, moduleName: string): Promise => { - log.push(`request import ${moduleName}`); - const promise = moduleName === "plugin-a" ? pluginADeferred.promise : pluginBDeferred.promise; - await promise; - log.push(`fulfill import ${moduleName}`); - return { - module: (() => { - log.push(`invoke plugin ${moduleName}`); - return { create: info => info.languageService }; - }) as ts.server.PluginModuleFactory, - error: undefined - }; - }; - - const { session } = setup(/*logLevel*/ undefined, { globalPlugins: ["plugin-a", "plugin-b"] }, importPlugin); - const projectService = session.getProjectService(); - - session.executeCommand({ seq: 1, type: "request", command: ts.server.protocol.CommandTypes.Open, arguments: { file: "^memfs:/foo.ts", content: "" } }); - - // wait a turn - await Promise.resolve(); - - // resolve imports out of order - pluginBDeferred.resolve(); - pluginADeferred.resolve(); - - // wait for load to complete - await projectService.waitForPendingPlugins(); - - expect(log).to.deep.equal([ - "request import plugin-a", - "request import plugin-b", - "fulfill import plugin-b", - "fulfill import plugin-a", - "invoke plugin plugin-a", - "invoke plugin plugin-b", - ]); - }); - - it("sends projectsUpdatedInBackground event", async () => { - const importPlugin = async (_root: string, _moduleName: string): Promise => { - await Promise.resolve(); // simulate at least a single turn delay - return { - module: (() => ({ create: info => info.languageService })) as ts.server.PluginModuleFactory, - error: undefined - }; - }; - - const { session, getMessages } = setup(/*logLevel*/ undefined, { globalPlugins: ["plugin-a"] }, importPlugin); - const projectService = session.getProjectService(); - - session.executeCommand({ seq: 1, type: "request", command: ts.server.protocol.CommandTypes.Open, arguments: { file: "^memfs:/foo.ts", content: "" } }); - - await projectService.waitForPendingPlugins(); - - expect(getMessages()).to.deep.equal([{ - seq: 0, - type: "event", - event: "projectsUpdatedInBackground", - body: { - openFiles: ["^memfs:/foo.ts"] - } - }]); - }); - - it("adds external files", async () => { - const pluginAShouldLoad = Utils.defer(); - const pluginAExternalFilesRequested = Utils.defer(); - - const importPlugin = async (_root: string, _moduleName: string): Promise => { - // wait until the initial external files are requested from the project service. - await pluginAShouldLoad.promise; - - return { - module: (() => ({ - create: info => info.languageService, - getExternalFiles: () => { - // signal that external files have been requested by the project service. - pluginAExternalFilesRequested.resolve(); - return ["external.txt"]; - } - })) as ts.server.PluginModuleFactory, - error: undefined - }; - }; - - const { session } = setup(/*logLevel*/ undefined, { globalPlugins: ["plugin-a"] }, importPlugin); - const projectService = session.getProjectService(); - - session.executeCommand({ seq: 1, type: "request", command: ts.server.protocol.CommandTypes.Open, arguments: { file: "^memfs:/foo.ts", content: "" } }); - - const project = projectService.inferredProjects[0]; - - // get the external files we know about before plugins are loaded - const initialExternalFiles = project.getExternalFiles(); - - // we've ready the initial set of external files, allow the plugin to continue loading. - pluginAShouldLoad.resolve(); - - // wait for plugins - await projectService.waitForPendingPlugins(); - - // wait for the plugin's external files to be requested - await pluginAExternalFilesRequested.promise; - - // get the external files we know aobut after plugins are loaded - const pluginExternalFiles = project.getExternalFiles(); - - expect(initialExternalFiles).to.deep.equal([]); - expect(pluginExternalFiles).to.deep.equal(["external.txt"]); - }); - - it("project is closed before plugins are loaded", async () => { - const pluginALoaded = Utils.defer(); - const projectClosed = Utils.defer(); - const importPlugin = async (_root: string, _moduleName: string): Promise => { - // mark that the plugin has started loading - pluginALoaded.resolve(); - - // wait until after a project close has been requested to continue - await projectClosed.promise; - return { - module: (() => ({ create: info => info.languageService })) as ts.server.PluginModuleFactory, - error: undefined - }; - }; - - const { session, getMessages } = setup(/*logLevel*/ undefined, { globalPlugins: ["plugin-a"] }, importPlugin); - const projectService = session.getProjectService(); - - session.executeCommand({ seq: 1, type: "request", command: ts.server.protocol.CommandTypes.Open, arguments: { file: "^memfs:/foo.ts", content: "" } }); - - // wait for the plugin to start loading - await pluginALoaded.promise; - - // close the project - session.executeCommand({ seq: 2, type: "request", command: ts.server.protocol.CommandTypes.Close, arguments: { file: "^memfs:/foo.ts" } }); - - // continue loading the plugin - projectClosed.resolve(); - - await projectService.waitForPendingPlugins(); - - // the project was closed before plugins were ready. no project update should have been requested - expect(getMessages()).not.to.deep.equal([{ - seq: 0, - type: "event", - event: "projectsUpdatedInBackground", - body: { - openFiles: ["^memfs:/foo.ts"] - } - }]); - }); - }); -}); diff --git a/src/tsconfig.json b/src/tsconfig.json index 1cfab491559d3..8603eaa5babfa 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -19,6 +19,5 @@ { "path": "./typingsInstaller" }, { "path": "./typingsInstallerCore" }, { "path": "./watchGuard" }, - { "path": "./webServer" }, ] } diff --git a/src/tsserver/_namespaces/ts.server.ts b/src/tsserver/_namespaces/ts.server.ts index 95d1804ff3fe2..18cb6abae0e42 100644 --- a/src/tsserver/_namespaces/ts.server.ts +++ b/src/tsserver/_namespaces/ts.server.ts @@ -2,7 +2,5 @@ export * from "../../jsTyping/_namespaces/ts.server"; export * from "../../server/_namespaces/ts.server"; -export * from "../../webServer/_namespaces/ts.server"; export * from "../nodeServer"; -export * from "../webServer"; export * from "../common"; diff --git a/src/tsserver/_namespaces/ts.ts b/src/tsserver/_namespaces/ts.ts index c37271569f120..f590869290d26 100644 --- a/src/tsserver/_namespaces/ts.ts +++ b/src/tsserver/_namespaces/ts.ts @@ -4,6 +4,5 @@ export * from "../../compiler/_namespaces/ts"; export * from "../../services/_namespaces/ts"; export * from "../../jsTyping/_namespaces/ts"; export * from "../../server/_namespaces/ts"; -export * from "../../webServer/_namespaces/ts"; import * as server from "./ts.server"; export { server }; diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index bcf2e0128ddae..d0e11077fcd9f 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -5,7 +5,6 @@ import { ActionPackageInstalled, ActionSet, Arguments, - BaseLogger, BeginInstallTypes, createInstallTypingsRequest, EndInstallTypes, @@ -27,6 +26,7 @@ import { LogLevel, ModuleImportResult, Msg, + nowString, nullCancellationToken, nullTypingsInstaller, PackageInstalledResponse, @@ -36,9 +36,9 @@ import { ServerCancellationToken, ServerHost, Session, + SessionOptions, SetTypings, StartInput, - StartSessionOptions, stringifyIndented, toEvent, TypesRegistryResponse, @@ -65,6 +65,7 @@ import { noopFileWatcher, normalizePath, normalizeSlashes, + perfLogger, resolveJSModule, SortedReadonlyArray, startTracing, @@ -165,6 +166,74 @@ function parseServerMode(): LanguageServiceMode | string | undefined { return mode; } } +/** @internal */ +export class BaseLogger implements Logger { + private seq = 0; + private inGroup = false; + private firstInGroup = true; + constructor(protected readonly level: LogLevel) { + } + static padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); + } + close() { + } + getLogFileName(): string | undefined { + return undefined; + } + 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 true; + } + hasLevel(level: LogLevel) { + return this.loggingEnabled() && this.level >= level; + } + 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; + } + + if (!this.canWrite()) return; + + s = `[${nowString()}] ${s}\n`; + if (!this.inGroup || this.firstInGroup) { + const prefix = BaseLogger.padStringRight(type + " " + this.seq.toString(), " "); + s = prefix + s; + } + this.write(s, type); + if (!this.inGroup) { + this.seq++; + } + } + protected canWrite() { + return true; + } + protected write(_s: string, _type: Msg) { + } +} /** @internal */ export function initializeNodeSystem(): StartInput { @@ -463,7 +532,18 @@ function parseEventPort(eventPortStr: string | undefined) { const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr); return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; } - +/** @internal */ +export interface StartSessionOptions { + globalPlugins: SessionOptions["globalPlugins"]; + pluginProbeLocations: SessionOptions["pluginProbeLocations"]; + allowLocalPluginLoads: SessionOptions["allowLocalPluginLoads"]; + useSingleInferredProject: SessionOptions["useSingleInferredProject"]; + useInferredProjectPerProjectRoot: SessionOptions["useInferredProjectPerProjectRoot"]; + suppressDiagnosticEvents: SessionOptions["suppressDiagnosticEvents"]; + noGetErrOnBackgroundUpdate: SessionOptions["noGetErrOnBackgroundUpdate"]; + syntaxOnly: SessionOptions["syntaxOnly"]; + serverMode: SessionOptions["serverMode"]; +} function startNodeSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) { const childProcess: { fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index c95b6b420d150..a07a98e6781c5 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -3,7 +3,6 @@ import { findArgument, hasArgument, initializeNodeSystem, - initializeWebSystem, Msg, StartInput, } from "./_namespaces/ts.server"; @@ -17,8 +16,6 @@ import { export * from "./_namespaces/ts"; -declare const addEventListener: any; -declare const removeEventListener: any; function findArgumentStringArray(argName: string): readonly string[] { const arg = findArgument(argName); if (arg === undefined) { @@ -73,16 +70,4 @@ function start({ args, logger, cancellationToken, serverMode, unknownServerMode, } setStackTraceLimit(); -// Cannot check process var directory in webworker so has to be typeof check here -if (typeof process !== "undefined") { - start(initializeNodeSystem(), require("os").platform()); -} -else { - // Get args from first message - const listener = (e: any) => { - removeEventListener("message", listener); - const args = e.data; - start(initializeWebSystem(args), "web"); - }; - addEventListener("message", listener); -} +start(initializeNodeSystem(), require("os").platform()); diff --git a/src/tsserver/tsconfig.json b/src/tsserver/tsconfig.json index f7a1d43e776fe..f24abdaf6fc07 100644 --- a/src/tsserver/tsconfig.json +++ b/src/tsserver/tsconfig.json @@ -11,7 +11,6 @@ { "path": "../services" }, { "path": "../jsTyping" }, { "path": "../server" }, - { "path": "../webServer" } ], "include": ["**/*"] } diff --git a/src/tsserver/webServer.ts b/src/tsserver/webServer.ts deleted file mode 100644 index d3ad989f20001..0000000000000 --- a/src/tsserver/webServer.ts +++ /dev/null @@ -1,163 +0,0 @@ -/// - -import * as ts from "./_namespaces/ts"; -import * as server from "./_namespaces/ts.server"; -import { - findArgument, - getLogLevel, - Logger, - MainProcessLogger, - Msg, - nullCancellationToken, - ServerCancellationToken, - ServerHost, - StartInput, - StartSessionOptions, - WebHost, -} from "./_namespaces/ts.server"; -import { - Debug, - LanguageServiceMode, - LogLevel, - noop, - returnFalse, - returnUndefined, - setSys, - sys, - validateLocaleAndSetLanguage, -} from "./_namespaces/ts"; - -const nullLogger: Logger = { - close: noop, - hasLevel: returnFalse, - loggingEnabled: returnFalse, - perftrc: noop, - info: noop, - msg: noop, - startGroup: noop, - endGroup: noop, - getLogFileName: returnUndefined, -}; - -function parseServerMode(): LanguageServiceMode | string | undefined { - const mode = findArgument("--serverMode"); - if (!mode) return undefined; - switch (mode.toLowerCase()) { - case "partialsemantic": - return LanguageServiceMode.PartialSemantic; - case "syntactic": - return LanguageServiceMode.Syntactic; - default: - return mode; - } -} - -/** @internal */ -export function initializeWebSystem(args: string[]): StartInput { - createWebSystem(args); - const modeOrUnknown = parseServerMode(); - let serverMode: LanguageServiceMode | undefined; - let unknownServerMode: string | undefined; - if (typeof modeOrUnknown === "number") serverMode = modeOrUnknown; - else unknownServerMode = modeOrUnknown; - const logger = createLogger(); - - // enable deprecation logging - Debug.loggingHost = { - log(level, s) { - switch (level) { - case LogLevel.Error: - case LogLevel.Warning: - return logger.msg(s, Msg.Err); - case LogLevel.Info: - case LogLevel.Verbose: - return logger.msg(s, Msg.Info); - } - } - }; - - return { - args, - logger, - cancellationToken: nullCancellationToken, - // Webserver defaults to partial semantic mode - serverMode: serverMode ?? LanguageServiceMode.PartialSemantic, - unknownServerMode, - startSession: startWebSession - }; -} - -function createLogger() { - const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity")); - return cmdLineVerbosity !== undefined ? new MainProcessLogger(cmdLineVerbosity, { writeMessage }) : nullLogger; -} - -function writeMessage(s: any) { - postMessage(s); -} - -function createWebSystem(args: string[]) { - Debug.assert(ts.sys === undefined); - const webHost: WebHost = { - readFile: webPath => { - const request = new XMLHttpRequest(); - request.open("GET", webPath, /* asynchronous */ false); - request.send(); - return request.status === 200 ? request.responseText : undefined; - }, - fileExists: webPath => { - const request = new XMLHttpRequest(); - request.open("HEAD", webPath, /* asynchronous */ false); - request.send(); - return request.status === 200; - }, - writeMessage, - }; - // Do this after sys has been set as findArguments is going to work only then - const sys = server.createWebSystem(webHost, args, () => findArgument("--executingFilePath") || location + ""); - setSys(sys); - const localeStr = findArgument("--locale"); - if (localeStr) { - validateLocaleAndSetLanguage(localeStr, sys); - } -} - -function hrtime(previous?: [number, number]) { - const now = self.performance.now() * 1e-3; - let seconds = Math.floor(now); - let nanoseconds = Math.floor((now % 1) * 1e9); - if (previous) { - seconds = seconds - previous[0]; - nanoseconds = nanoseconds - previous[1]; - if (nanoseconds < 0) { - seconds--; - nanoseconds += 1e9; - } - } - return [seconds, nanoseconds]; -} - -function startWebSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) { - class WorkerSession extends server.WorkerSession { - constructor() { - super(sys as ServerHost, { writeMessage }, options, logger, cancellationToken, hrtime); - } - - exit() { - this.logger.info("Exiting..."); - this.projectService.closeLog(); - close(); - } - - listen() { - addEventListener("message", (message: any) => { - this.onMessage(message.data); - }); - } - } - - const session = new WorkerSession(); - - // Start listening - session.listen(); -} diff --git a/src/webServer/_namespaces/ts.server.ts b/src/webServer/_namespaces/ts.server.ts deleted file mode 100644 index d21d521a900f5..0000000000000 --- a/src/webServer/_namespaces/ts.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* Generated file to emulate the ts.server namespace. */ - -export * from "../../jsTyping/_namespaces/ts.server"; -export * from "../../server/_namespaces/ts.server"; -export * from "../webServer"; diff --git a/src/webServer/_namespaces/ts.ts b/src/webServer/_namespaces/ts.ts deleted file mode 100644 index 3060a64378787..0000000000000 --- a/src/webServer/_namespaces/ts.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* Generated file to emulate the ts namespace. */ - -export * from "../../compiler/_namespaces/ts"; -export * from "../../jsTyping/_namespaces/ts"; -export * from "../../services/_namespaces/ts"; -export * from "../../server/_namespaces/ts"; -import * as server from "./ts.server"; -export { server }; diff --git a/src/webServer/tsconfig.json b/src/webServer/tsconfig.json deleted file mode 100644 index 5b4c1fdbf5605..0000000000000 --- a/src/webServer/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig-base", - "compilerOptions": { - "types": [ - "node" - ] - }, - "references": [ - { "path": "../compiler" }, - { "path": "../jsTyping" }, - { "path": "../services" }, - { "path": "../server" } - ], - "include": ["**/*"] -} diff --git a/src/webServer/webServer.ts b/src/webServer/webServer.ts deleted file mode 100644 index f5cc041c7b601..0000000000000 --- a/src/webServer/webServer.ts +++ /dev/null @@ -1,279 +0,0 @@ -/// -/// - -import { - indent, - Logger, - LogLevel, - ModuleImportResult, - Msg, - nowString, - nullTypingsInstaller, - protocol, - ServerCancellationToken, - ServerHost, - Session, - SessionOptions, -} from "./_namespaces/ts.server"; -import { - combinePaths, - Debug, - directorySeparator, - ensureTrailingDirectorySeparator, - getDirectoryPath, - identity, - memoize, - notImplemented, - perfLogger, - returnFalse, - returnNoopFileWatcher, - startsWith, -} from "./_namespaces/ts"; - -/** @internal */ -export interface HostWithWriteMessage { - writeMessage(s: any): void; -} -/** @internal */ -export interface WebHost extends HostWithWriteMessage { - readFile(path: string): string | undefined; - fileExists(path: string): boolean; -} - -/** @internal */ -export class BaseLogger implements Logger { - private seq = 0; - private inGroup = false; - private firstInGroup = true; - constructor(protected readonly level: LogLevel) { - } - static padStringRight(str: string, padding: string) { - return (str + padding).slice(0, padding.length); - } - close() { - } - getLogFileName(): string | undefined { - return undefined; - } - 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 true; - } - hasLevel(level: LogLevel) { - return this.loggingEnabled() && this.level >= level; - } - 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; - } - - if (!this.canWrite()) return; - - s = `[${nowString()}] ${s}\n`; - if (!this.inGroup || this.firstInGroup) { - const prefix = BaseLogger.padStringRight(type + " " + this.seq.toString(), " "); - s = prefix + s; - } - this.write(s, type); - if (!this.inGroup) { - this.seq++; - } - } - protected canWrite() { - return true; - } - protected write(_s: string, _type: Msg) { - } -} - -/** @internal */ -export type MessageLogLevel = "info" | "perf" | "error"; -/** @internal */ -export interface LoggingMessage { - readonly type: "log"; - readonly level: MessageLogLevel; - readonly body: string -} -/** @internal */ -export class MainProcessLogger extends BaseLogger { - constructor(level: LogLevel, private host: HostWithWriteMessage) { - super(level); - } - protected write(body: string, type: Msg) { - let level: MessageLogLevel; - switch (type) { - case Msg.Info: - level = "info"; - break; - case Msg.Perf: - level = "perf"; - break; - case Msg.Err: - level = "error"; - break; - default: - Debug.assertNever(type); - } - this.host.writeMessage({ - type: "log", - level, - body, - } as LoggingMessage); - } -} - -/** @internal */ -export function createWebSystem(host: WebHost, args: string[], getExecutingFilePath: () => string): ServerHost { - const returnEmptyString = () => ""; - const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(getExecutingFilePath())))); - // Later we could map ^memfs:/ to do something special if we want to enable more functionality like module resolution or something like that - const getWebPath = (path: string) => startsWith(path, directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; - - return { - args, - newLine: "\r\n", // This can be configured by clients - useCaseSensitiveFileNames: false, // Use false as the default on web since that is the safest option - readFile: path => { - const webPath = getWebPath(path); - return webPath && host.readFile(webPath); - }, - write: host.writeMessage.bind(host), - watchFile: returnNoopFileWatcher, - watchDirectory: returnNoopFileWatcher, - - getExecutingFilePath: () => directorySeparator, - getCurrentDirectory: returnEmptyString, // For inferred project root if projectRoot path is not set, normalizing the paths - - /* eslint-disable no-restricted-globals */ - setTimeout: (cb, ms, ...args) => setTimeout(cb, ms, ...args), - clearTimeout: handle => clearTimeout(handle), - setImmediate: x => setTimeout(x, 0), - clearImmediate: handle => clearTimeout(handle), - /* eslint-enable no-restricted-globals */ - - importPlugin: async (initialDir: string, moduleName: string): Promise => { - const packageRoot = combinePaths(initialDir, moduleName); - - let packageJson: any | undefined; - try { - const packageJsonResponse = await fetch(combinePaths(packageRoot, "package.json")); - packageJson = await packageJsonResponse.json(); - } - catch (e) { - return { module: undefined, error: new Error("Could not load plugin. Could not load 'package.json'.") }; - } - - const browser = packageJson.browser; - if (!browser) { - return { module: undefined, error: new Error("Could not load plugin. No 'browser' field found in package.json.") }; - } - - const scriptPath = combinePaths(packageRoot, browser); - try { - const { default: module } = await import(scriptPath); - return { module, error: undefined }; - } - catch (e) { - return { module: undefined, error: e }; - } - }, - exit: notImplemented, - - // Debugging related - getEnvironmentVariable: returnEmptyString, // TODO:: Used to enable debugging info - // tryEnableSourceMapsForHost?(): void; - // debugMode?: boolean; - - // For semantic server mode - fileExists: path => { - const webPath = getWebPath(path); - return !!webPath && host.fileExists(webPath); - }, - directoryExists: returnFalse, // Module resolution - readDirectory: notImplemented, // Configured project, typing installer - getDirectories: () => [], // For automatic type reference directives - createDirectory: notImplemented, // compile On save - writeFile: notImplemented, // compile on save - resolvePath: identity, // Plugins - // realpath? // Module resolution, symlinks - // getModifiedTime // File watching - // createSHA256Hash // telemetry of the project - - // Logging related - // /** @internal */ bufferFrom?(input: string, encoding?: string): Buffer; - // gc?(): void; - // getMemoryUsage?(): number; - }; -} - -/** @internal */ -export interface StartSessionOptions { - globalPlugins: SessionOptions["globalPlugins"]; - pluginProbeLocations: SessionOptions["pluginProbeLocations"]; - allowLocalPluginLoads: SessionOptions["allowLocalPluginLoads"]; - useSingleInferredProject: SessionOptions["useSingleInferredProject"]; - useInferredProjectPerProjectRoot: SessionOptions["useInferredProjectPerProjectRoot"]; - suppressDiagnosticEvents: SessionOptions["suppressDiagnosticEvents"]; - noGetErrOnBackgroundUpdate: SessionOptions["noGetErrOnBackgroundUpdate"]; - syntaxOnly: SessionOptions["syntaxOnly"]; - serverMode: SessionOptions["serverMode"]; -} -/** @internal */ -export class WorkerSession extends Session<{}> { - constructor(host: ServerHost, private webHost: HostWithWriteMessage, options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, hrtime: SessionOptions["hrtime"]) { - super({ - host, - cancellationToken, - ...options, - typingsInstaller: nullTypingsInstaller, - byteLength: notImplemented, // Formats the message text in send of Session which is overriden in this class so not needed - hrtime, - logger, - canUseEvents: true, - }); - } - - public send(msg: protocol.Message) { - if (msg.type === "event" && !this.canUseEvents) { - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); - } - return; - } - if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`${msg.type}:${indent(JSON.stringify(msg))}`); - } - this.webHost.writeMessage(msg); - } - - protected parseMessage(message: {}): protocol.Request { - return message as protocol.Request; - } - - protected toStringMessage(message: {}) { - return JSON.stringify(message, undefined, 2); - } -} From e8b0f679091b6f578c924eb0d1c3d5ff17b7781d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 30 Nov 2022 10:59:21 -0800 Subject: [PATCH 2/5] Refactor moved code 1. Move StartSessionOptions to common next to where it's first used. 2. Inline single-use BaseLogger base class into its only child class, Logger. 3. Start using direct imports, eg `import {} from './common'`. I hope this is OK?! --- src/tsserver/common.ts | 15 +++- src/tsserver/nodeServer.ts | 146 ++++++++++++++----------------------- 2 files changed, 70 insertions(+), 91 deletions(-) diff --git a/src/tsserver/common.ts b/src/tsserver/common.ts index f62f1316de1f3..109f255e6cfaf 100644 --- a/src/tsserver/common.ts +++ b/src/tsserver/common.ts @@ -2,7 +2,7 @@ import { Logger, LogLevel, ServerCancellationToken, - StartSessionOptions, + SessionOptions, } from "./_namespaces/ts.server"; import { LanguageServiceMode } from "./_namespaces/ts"; @@ -19,6 +19,19 @@ export function getLogLevel(level: string | undefined) { return undefined; } +/** @internal */ +export interface StartSessionOptions { + globalPlugins: SessionOptions["globalPlugins"]; + pluginProbeLocations: SessionOptions["pluginProbeLocations"]; + allowLocalPluginLoads: SessionOptions["allowLocalPluginLoads"]; + useSingleInferredProject: SessionOptions["useSingleInferredProject"]; + useInferredProjectPerProjectRoot: SessionOptions["useInferredProjectPerProjectRoot"]; + suppressDiagnosticEvents: SessionOptions["suppressDiagnosticEvents"]; + noGetErrOnBackgroundUpdate: SessionOptions["noGetErrOnBackgroundUpdate"]; + syntaxOnly: SessionOptions["syntaxOnly"]; + serverMode: SessionOptions["serverMode"]; +} + /** @internal */ export interface StartInput { args: readonly string[]; diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index d0e11077fcd9f..2df5d87ed2261 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -36,9 +36,7 @@ import { ServerCancellationToken, ServerHost, Session, - SessionOptions, SetTypings, - StartInput, stringifyIndented, toEvent, TypesRegistryResponse, @@ -78,6 +76,10 @@ import { versionMajorMinor, WatchOptions, } from "./_namespaces/ts"; +import { + StartInput, + StartSessionOptions, +} from './common' interface LogOptions { file?: string; @@ -166,74 +168,6 @@ function parseServerMode(): LanguageServiceMode | string | undefined { return mode; } } -/** @internal */ -export class BaseLogger implements Logger { - private seq = 0; - private inGroup = false; - private firstInGroup = true; - constructor(protected readonly level: LogLevel) { - } - static padStringRight(str: string, padding: string) { - return (str + padding).slice(0, padding.length); - } - close() { - } - getLogFileName(): string | undefined { - return undefined; - } - 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 true; - } - hasLevel(level: LogLevel) { - return this.loggingEnabled() && this.level >= level; - } - 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; - } - - if (!this.canWrite()) return; - - s = `[${nowString()}] ${s}\n`; - if (!this.inGroup || this.firstInGroup) { - const prefix = BaseLogger.padStringRight(type + " " + this.seq.toString(), " "); - s = prefix + s; - } - this.write(s, type); - if (!this.inGroup) { - this.seq++; - } - } - protected canWrite() { - return true; - } - protected write(_s: string, _type: Msg) { - } -} /** @internal */ export function initializeNodeSystem(): StartInput { @@ -274,14 +208,16 @@ export function initializeNodeSystem(): StartInput { stat(path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any): void; } = require("fs"); - class Logger extends BaseLogger { + class Logger implements Logger { + private seq = 0; + private inGroup = false; + private firstInGroup = true; private fd = -1; constructor( private readonly logFilename: string, private readonly traceToConsole: boolean, - level: LogLevel + private readonly level: LogLevel ) { - super(level); if (this.logFilename) { try { this.fd = fs.openSync(this.logFilename, "w"); @@ -291,25 +227,67 @@ export function initializeNodeSystem(): StartInput { } } } - + static padStringRight(str: string, padding: string) { + return (str + padding).slice(0, padding.length); + } close() { if (this.fd >= 0) { fs.close(this.fd, noop); } } - - getLogFileName() { + getLogFileName(): string | undefined { 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; } + hasLevel(level: LogLevel) { + return this.loggingEnabled() && this.level >= level; + } + 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; + } + if (!this.canWrite()) return; + + s = `[${nowString()}] ${s}\n`; + if (!this.inGroup || this.firstInGroup) { + const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " "); + s = prefix + s; + } + this.write(s, type); + if (!this.inGroup) { + this.seq++; + } + } protected canWrite() { return this.fd >= 0 || this.traceToConsole; } - protected write(s: string, _type: Msg) { if (this.fd >= 0) { const buf = sys.bufferFrom!(s); @@ -532,18 +510,6 @@ function parseEventPort(eventPortStr: string | undefined) { const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr); return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined; } -/** @internal */ -export interface StartSessionOptions { - globalPlugins: SessionOptions["globalPlugins"]; - pluginProbeLocations: SessionOptions["pluginProbeLocations"]; - allowLocalPluginLoads: SessionOptions["allowLocalPluginLoads"]; - useSingleInferredProject: SessionOptions["useSingleInferredProject"]; - useInferredProjectPerProjectRoot: SessionOptions["useInferredProjectPerProjectRoot"]; - suppressDiagnosticEvents: SessionOptions["suppressDiagnosticEvents"]; - noGetErrOnBackgroundUpdate: SessionOptions["noGetErrOnBackgroundUpdate"]; - syntaxOnly: SessionOptions["syntaxOnly"]; - serverMode: SessionOptions["serverMode"]; -} function startNodeSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) { const childProcess: { fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike }): NodeChildProcess; From d0e76faccbb7f15d6f997c56f95a7fad90d09b63 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 30 Nov 2022 11:49:41 -0800 Subject: [PATCH 3/5] Fix lint --- src/tsserver/nodeServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index 2df5d87ed2261..1247a977bd914 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -79,7 +79,7 @@ import { import { StartInput, StartSessionOptions, -} from './common' +} from "./common"; interface LogOptions { file?: string; From 2c6e29612addb838c5b5e5e07fe12b2529db1edb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 30 Nov 2022 11:59:26 -0800 Subject: [PATCH 4/5] move imports back to namespace import --- src/tsserver/nodeServer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tsserver/nodeServer.ts b/src/tsserver/nodeServer.ts index 1247a977bd914..29ed8aaa81109 100644 --- a/src/tsserver/nodeServer.ts +++ b/src/tsserver/nodeServer.ts @@ -37,6 +37,8 @@ import { ServerHost, Session, SetTypings, + StartInput, + StartSessionOptions, stringifyIndented, toEvent, TypesRegistryResponse, @@ -76,10 +78,6 @@ import { versionMajorMinor, WatchOptions, } from "./_namespaces/ts"; -import { - StartInput, - StartSessionOptions, -} from "./common"; interface LogOptions { file?: string; From 2bb2e30dce2678e762c4e3939b5d524ff6ed670f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 1 Dec 2022 10:04:05 -0800 Subject: [PATCH 5/5] hereby tsserver: remove exportIsTsObject --- Herebyfile.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Herebyfile.mjs b/Herebyfile.mjs index e047f31f22a64..79d3e9d9de9ed 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -416,11 +416,6 @@ const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({ builtEntrypoint: "./built/local/tsserver/server.js", output: "./built/local/tsserver.js", mainDeps: [generateLibs], - // Even though this seems like an exectuable, so could be the default CJS, - // this is used in the browser too. Do the same thing that we do for our - // libraries and generate an IIFE with name `ts`, as to not pollute the global - // scope. - bundlerOptions: { exportIsTsObject: true }, }); export { tsserver, watchTsserver };