Remove webServer (#51699)

* Remove webServer

First draft; I may move some things around to be more readable.

* 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?!

* Fix lint

* move imports back to namespace import

* hereby tsserver: remove exportIsTsObject
This commit is contained in:
Nathan Shively-Sanders 2022-12-06 08:41:01 -08:00 committed by GitHub
parent c124d0e260
commit 5bb204e321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 69 additions and 883 deletions

View File

@ -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 };

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -11,7 +11,6 @@
{ "path": "../services" },
{ "path": "../jsTyping" },
{ "path": "../server" },
{ "path": "../webServer" },
{ "path": "../typingsInstallerCore" },
{ "path": "../deprecatedCompat" },
{ "path": "../harness" },

View File

@ -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<ts.server.StartSessionOptions>, 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<ts.server.StartSessionOptions>, 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<string> = [];"
};
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<T> { 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<ts.server.ModuleImportResult> => {
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<ts.server.ModuleImportResult> => {
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<ts.server.ModuleImportResult> => {
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<ts.server.ModuleImportResult> => {
// 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<ts.server.ModuleImportResult> => {
// 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"]
}
}]);
});
});
});

View File

@ -19,6 +19,5 @@
{ "path": "./typingsInstaller" },
{ "path": "./typingsInstallerCore" },
{ "path": "./watchGuard" },
{ "path": "./webServer" },
]
}

View File

@ -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";

View File

@ -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 };

View File

@ -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[];

View File

@ -5,7 +5,6 @@ import {
ActionPackageInstalled,
ActionSet,
Arguments,
BaseLogger,
BeginInstallTypes,
createInstallTypingsRequest,
EndInstallTypes,
@ -27,6 +26,7 @@ import {
LogLevel,
ModuleImportResult,
Msg,
nowString,
nullCancellationToken,
nullTypingsInstaller,
PackageInstalledResponse,
@ -65,6 +65,7 @@ import {
noopFileWatcher,
normalizePath,
normalizeSlashes,
perfLogger,
resolveJSModule,
SortedReadonlyArray,
startTracing,
@ -205,14 +206,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");
@ -222,25 +225,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);
@ -463,7 +508,6 @@ function parseEventPort(eventPortStr: string | undefined) {
const eventPort = eventPortStr === undefined ? undefined : parseInt(eventPortStr);
return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined;
}
function startNodeSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
const childProcess: {
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;

View File

@ -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());

View File

@ -11,7 +11,6 @@
{ "path": "../services" },
{ "path": "../jsTyping" },
{ "path": "../server" },
{ "path": "../webServer" }
],
"include": ["**/*"]
}

View File

@ -1,163 +0,0 @@
/// <reference lib="webworker" />
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();
}

View File

@ -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";

View File

@ -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 };

View File

@ -1,15 +0,0 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"types": [
"node"
]
},
"references": [
{ "path": "../compiler" },
{ "path": "../jsTyping" },
{ "path": "../services" },
{ "path": "../server" }
],
"include": ["**/*"]
}

View File

@ -1,279 +0,0 @@
/// <reference lib="dom" />
/// <reference lib="webworker.importscripts" />
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<ModuleImportResult> => {
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);
}
}