microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
064161d8277a88b3facccca58df87aa332bf9187

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/compiler/server/server.ts

151lines · modecode

1import { Console } from "console";
2import { writeFile } from "fs/promises";
3import inspector from "inspector";
4import mkdirp from "mkdirp";
5import { join } from "path";
6import { fileURLToPath } from "url";
7import { TextDocument } from "vscode-languageserver-textdocument";
8import {
9 createConnection,
10 ProposedFeatures,
11 PublishDiagnosticsParams,
12 TextDocuments,
13} from "vscode-languageserver/node.js";
14import { NodeHost } from "../core/node-host.js";
15import { typespecVersion } from "../core/util.js";
16import { createServer, Server, ServerHost } from "./serverlib.js";
17
18let server: Server | undefined = undefined;
19
20const profileDir = process.env.TYPESPEC_SERVER_PROFILE_DIR;
21const logTiming = process.env.TYPESPEC_SERVER_LOG_TIMING === "true";
22let profileSession: inspector.Session | undefined;
23
24process.on("unhandledRejection", fatalError);
25try {
26 main();
27} catch (e) {
28 fatalError(e);
29}
30
31function main() {
32 // Redirect all console stdout output to stderr since LSP pipe uses stdout
33 // and writing to stdout for anything other than LSP protocol will break
34 // things badly.
35 global.console = new Console(process.stderr, process.stderr);
36
37 let clientHasWorkspaceFolderCapability = false;
38 const connection = createConnection(ProposedFeatures.all);
39 const documents = new TextDocuments(TextDocument);
40
41 const host: ServerHost = {
42 compilerHost: NodeHost,
43 sendDiagnostics(params: PublishDiagnosticsParams) {
44 void connection.sendDiagnostics(params);
45 },
46 log(message: string) {
47 connection.console.log(message);
48 },
49 getOpenDocumentByURL(url: string) {
50 return documents.get(url);
51 },
52 };
53
54 const s = createServer(host);
55 server = s;
56 s.log(`TypeSpec language server v${typespecVersion}`);
57 s.log("Module", fileURLToPath(import.meta.url));
58 s.log("Process ID", process.pid);
59 s.log("Command Line", process.argv);
60
61 if (profileDir) {
62 s.log("CPU profiling enabled", profileDir);
63 profileSession = new inspector.Session();
64 profileSession.connect();
65 }
66
67 connection.onInitialize(async (params) => {
68 if (params.capabilities.workspace?.workspaceFolders) {
69 clientHasWorkspaceFolderCapability = true;
70 }
71 return await s.initialize(params);
72 });
73
74 connection.onInitialized((params) => {
75 if (clientHasWorkspaceFolderCapability) {
76 connection.workspace.onDidChangeWorkspaceFolders(s.workspaceFoldersChanged);
77 }
78 s.initialized(params);
79 });
80
81 connection.onDocumentFormatting(profile(s.formatDocument));
82 connection.onDidChangeWatchedFiles(profile(s.watchedFilesChanged));
83 connection.onDefinition(profile(s.gotoDefinition));
84 connection.onCompletion(profile(s.complete));
85 connection.onReferences(profile(s.findReferences));
86 connection.onRenameRequest(profile(s.rename));
87 connection.onPrepareRename(profile(s.prepareRename));
88 connection.onFoldingRanges(profile(s.getFoldingRanges));
89 connection.onDocumentSymbol(profile(s.getDocumentSymbols));
90 connection.onDocumentHighlight(profile(s.findDocumentHighlight));
91 connection.onHover(profile(s.getHover));
92 connection.onSignatureHelp(profile(s.getSignatureHelp));
93 connection.languages.semanticTokens.on(profile(s.buildSemanticTokens));
94
95 documents.onDidChangeContent(profile(s.checkChange));
96 documents.onDidClose(profile(s.documentClosed));
97
98 documents.listen(connection);
99 connection.listen();
100}
101
102function fatalError(e: unknown) {
103 // If we failed to send any log messages over LSP pipe, send them to
104 // stderr before exiting.
105 for (const pending of server?.pendingMessages ?? []) {
106 // eslint-disable-next-line no-console
107 console.error(pending);
108 }
109 // eslint-disable-next-line no-console
110 console.error(e);
111 process.exit(1);
112}
113
114function profile<T extends (...args: any) => any>(func: T): T {
115 const name = func.name;
116
117 if (logTiming) {
118 func = time(func);
119 }
120
121 if (!profileDir) {
122 return func;
123 }
124
125 return (async (...args: any[]) => {
126 profileSession!.post("Profiler.enable", () => {
127 // eslint-disable-next-line @typescript-eslint/no-misused-promises
128 profileSession!.post("Profiler.start", async () => {
129 const ret = await func.apply(undefined!, args);
130 // eslint-disable-next-line @typescript-eslint/no-misused-promises
131 profileSession!.post("Profiler.stop", async (err, args) => {
132 if (!err && args.profile) {
133 await mkdirp(profileDir!);
134 await writeFile(join(profileDir!, name + ".cpuprofile"), JSON.stringify(args.profile));
135 }
136 });
137 return ret;
138 });
139 });
140 }) as T;
141}
142
143function time<T extends (...args: any) => any>(func: T): T {
144 return (async (...args: any[]) => {
145 const start = Date.now();
146 const ret = await func.apply(undefined!, args);
147 const end = Date.now();
148 server!.log(func.name, end - start + " ms");
149 return ret;
150 }) as T;
151}
152