1/*@internal*/ 2namespace ts.server { 3 export interface HostWithWriteMessage { 4 writeMessage(s: any): void; 5 } 6 export interface WebHost extends HostWithWriteMessage { 7 readFile(path: string): string | undefined; 8 fileExists(path: string): boolean; 9 } 10 11 export class BaseLogger implements Logger { 12 private seq = 0; 13 private inGroup = false; 14 private firstInGroup = true; 15 constructor(protected readonly level: LogLevel) { 16 } 17 static padStringRight(str: string, padding: string) { 18 return (str + padding).slice(0, padding.length); 19 } 20 close() { 21 } 22 getLogFileName(): string | undefined { 23 return undefined; 24 } 25 perftrc(s: string) { 26 this.msg(s, Msg.Perf); 27 } 28 info(s: string) { 29 this.msg(s, Msg.Info); 30 } 31 err(s: string) { 32 this.msg(s, Msg.Err); 33 } 34 startGroup() { 35 this.inGroup = true; 36 this.firstInGroup = true; 37 } 38 endGroup() { 39 this.inGroup = false; 40 } 41 loggingEnabled() { 42 return true; 43 } 44 hasLevel(level: LogLevel) { 45 return this.loggingEnabled() && this.level >= level; 46 } 47 msg(s: string, type: Msg = Msg.Err) { 48 switch (type) { 49 case Msg.Info: 50 perfLogger.logInfoEvent(s); 51 break; 52 case Msg.Perf: 53 perfLogger.logPerfEvent(s); 54 break; 55 default: // Msg.Err 56 perfLogger.logErrEvent(s); 57 break; 58 } 59 60 if (!this.canWrite()) return; 61 62 s = `[${nowString()}] ${s}\n`; 63 if (!this.inGroup || this.firstInGroup) { 64 const prefix = BaseLogger.padStringRight(type + " " + this.seq.toString(), " "); 65 s = prefix + s; 66 } 67 this.write(s, type); 68 if (!this.inGroup) { 69 this.seq++; 70 } 71 } 72 protected canWrite() { 73 return true; 74 } 75 protected write(_s: string, _type: Msg) { 76 } 77 } 78 79 export type MessageLogLevel = "info" | "perf" | "error"; 80 export interface LoggingMessage { 81 readonly type: "log"; 82 readonly level: MessageLogLevel; 83 readonly body: string 84 } 85 export class MainProcessLogger extends BaseLogger { 86 constructor(level: LogLevel, private host: HostWithWriteMessage) { 87 super(level); 88 } 89 protected write(body: string, type: Msg) { 90 let level: MessageLogLevel; 91 switch (type) { 92 case Msg.Info: 93 level = "info"; 94 break; 95 case Msg.Perf: 96 level = "perf"; 97 break; 98 case Msg.Err: 99 level = "error"; 100 break; 101 default: 102 Debug.assertNever(type); 103 } 104 this.host.writeMessage(<LoggingMessage>{ 105 type: "log", 106 level, 107 body, 108 }); 109 } 110 } 111 112 export function createWebSystem(host: WebHost, args: string[], getExecutingFilePath: () => string): ServerHost { 113 const returnEmptyString = () => ""; 114 const getExecutingDirectoryPath = memoize(() => memoize(() => ensureTrailingDirectorySeparator(getDirectoryPath(getExecutingFilePath())))); 115 // Later we could map ^memfs:/ to do something special if we want to enable more functionality like module resolution or something like that 116 const getWebPath = (path: string) => startsWith(path, directorySeparator) ? path.replace(directorySeparator, getExecutingDirectoryPath()) : undefined; 117 return { 118 args, 119 newLine: "\r\n", // This can be configured by clients 120 useCaseSensitiveFileNames: false, // Use false as the default on web since that is the safest option 121 readFile: path => { 122 const webPath = getWebPath(path); 123 return webPath && host.readFile(webPath); 124 }, 125 126 write: host.writeMessage.bind(host), 127 watchFile: returnNoopFileWatcher, 128 watchDirectory: returnNoopFileWatcher, 129 130 getExecutingFilePath: () => directorySeparator, 131 getCurrentDirectory: returnEmptyString, // For inferred project root if projectRoot path is not set, normalizing the paths 132 133 /* eslint-disable no-restricted-globals */ 134 setTimeout, 135 clearTimeout, 136 setImmediate: x => setTimeout(x, 0), 137 clearImmediate: clearTimeout, 138 /* eslint-enable no-restricted-globals */ 139 140 require: () => ({ module: undefined, error: new Error("Not implemented") }), 141 exit: notImplemented, 142 143 // Debugging related 144 getEnvironmentVariable: returnEmptyString, // TODO:: Used to enable debugging info 145 // tryEnableSourceMapsForHost?(): void; 146 // debugMode?: boolean; 147 148 // For semantic server mode 149 fileExists: path => { 150 const webPath = getWebPath(path); 151 return !!webPath && host.fileExists(webPath); 152 }, 153 directoryExists: returnFalse, // Module resolution 154 readDirectory: notImplemented, // Configured project, typing installer 155 getDirectories: () => [], // For automatic type reference directives 156 createDirectory: notImplemented, // compile On save 157 writeFile: notImplemented, // compile on save 158 resolvePath: identity, // Plugins 159 // realpath? // Module resolution, symlinks 160 // getModifiedTime // File watching 161 // createSHA256Hash // telemetry of the project 162 163 // Logging related 164 // /*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer; 165 // gc?(): void; 166 // getMemoryUsage?(): number; 167 }; 168 } 169 170 export interface StartSessionOptions { 171 globalPlugins: SessionOptions["globalPlugins"]; 172 pluginProbeLocations: SessionOptions["pluginProbeLocations"]; 173 allowLocalPluginLoads: SessionOptions["allowLocalPluginLoads"]; 174 useSingleInferredProject: SessionOptions["useSingleInferredProject"]; 175 useInferredProjectPerProjectRoot: SessionOptions["useInferredProjectPerProjectRoot"]; 176 suppressDiagnosticEvents: SessionOptions["suppressDiagnosticEvents"]; 177 noGetErrOnBackgroundUpdate: SessionOptions["noGetErrOnBackgroundUpdate"]; 178 syntaxOnly: SessionOptions["syntaxOnly"]; 179 serverMode: SessionOptions["serverMode"]; 180 } 181 export class WorkerSession extends Session<{}> { 182 constructor(host: ServerHost, private webHost: HostWithWriteMessage, options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, hrtime: SessionOptions["hrtime"]) { 183 super({ 184 host, 185 cancellationToken, 186 ...options, 187 typingsInstaller: nullTypingsInstaller, 188 byteLength: notImplemented, // Formats the message text in send of Session which is overriden in this class so not needed 189 hrtime, 190 logger, 191 canUseEvents: false, 192 }); 193 } 194 195 public send(msg: protocol.Message) { 196 if (msg.type === "event" && !this.canUseEvents) { 197 if (this.logger.hasLevel(LogLevel.verbose)) { 198 this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); 199 } 200 return; 201 } 202 if (this.logger.hasLevel(LogLevel.verbose)) { 203 this.logger.info(`${msg.type}:${indent(JSON.stringify(msg))}`); 204 } 205 this.webHost.writeMessage(msg); 206 } 207 208 protected parseMessage(message: {}): protocol.Request { 209 return <protocol.Request>message; 210 } 211 212 protected toStringMessage(message: {}) { 213 return JSON.stringify(message, undefined, 2); 214 } 215 } 216} 217