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