/*@internal*/ namespace ts.server { export interface HostWithWriteMessage { writeMessage(s: any): void; } export interface WebHost extends HostWithWriteMessage { readFile(path: string): string | undefined; fileExists(path: string): boolean; } 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) { } } export type MessageLogLevel = "info" | "perf" | "error"; export interface LoggingMessage { readonly type: "log"; readonly level: MessageLogLevel; readonly body: string } 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, }); } } 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, clearTimeout, setImmediate: x => setTimeout(x, 0), clearImmediate: clearTimeout, /* eslint-enable no-restricted-globals */ require: () => ({ module: undefined, error: new Error("Not implemented") }), 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; }; } 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"]; } 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: false, }); } 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; } protected toStringMessage(message: {}) { return JSON.stringify(message, undefined, 2); } } }