mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 03:09:39 -06:00
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:
parent
c124d0e260
commit
5bb204e321
@ -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 };
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
{ "path": "../services" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../server" },
|
||||
{ "path": "../webServer" },
|
||||
{ "path": "../typingsInstallerCore" },
|
||||
{ "path": "../deprecatedCompat" },
|
||||
{ "path": "../harness" },
|
||||
|
||||
@ -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"]
|
||||
}
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -19,6 +19,5 @@
|
||||
{ "path": "./typingsInstaller" },
|
||||
{ "path": "./typingsInstallerCore" },
|
||||
{ "path": "./watchGuard" },
|
||||
{ "path": "./webServer" },
|
||||
]
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
{ "path": "../services" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../server" },
|
||||
{ "path": "../webServer" }
|
||||
],
|
||||
"include": ["**/*"]
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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";
|
||||
@ -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 };
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../compiler" },
|
||||
{ "path": "../jsTyping" },
|
||||
{ "path": "../services" },
|
||||
{ "path": "../server" }
|
||||
],
|
||||
"include": ["**/*"]
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user