• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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