@typespec/compiler
Version:
TypeSpec Compiler Preview
167 lines • 7 kB
JavaScript
import { Console } from "console";
import { mkdir, writeFile } from "fs/promises";
import inspector from "inspector";
import { join } from "path";
import { fileURLToPath } from "url";
import { inspect } from "util";
import { TextDocument } from "vscode-languageserver-textdocument";
import { ProposedFeatures, TextDocuments, createConnection, } from "vscode-languageserver/node.js";
import { NodeHost } from "../core/node-host.js";
import { typespecVersion } from "../manifest.js";
import { createServer } from "./serverlib.js";
let server = undefined;
const profileDir = process.env.TYPESPEC_SERVER_PROFILE_DIR;
const logTiming = process.env.TYPESPEC_SERVER_LOG_TIMING === "true";
let profileSession;
process.on("unhandledRejection", fatalError);
try {
main();
}
catch (e) {
fatalError(e);
}
function main() {
// Redirect all console stdout output to stderr since LSP pipe uses stdout
// and writing to stdout for anything other than LSP protocol will break
// things badly.
global.console = new Console(process.stderr, process.stderr);
let clientHasWorkspaceFolderCapability = false;
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
const host = {
compilerHost: NodeHost,
sendDiagnostics(params) {
void connection.sendDiagnostics(params);
},
log(log) {
const message = log.message;
let detail = undefined;
let fullMessage = message;
if (log.detail) {
detail = typeof log.detail === "string" ? log.detail : inspect(log.detail);
fullMessage = `${message}:\n${detail}`;
}
switch (log.level) {
case "trace":
connection.tracer.log(message, detail);
break;
case "debug":
connection.console.debug(fullMessage);
break;
case "info":
connection.console.info(fullMessage);
break;
case "warning":
connection.console.warn(fullMessage);
break;
case "error":
connection.console.error(fullMessage);
break;
default:
connection.console.error(`Log Message with invalid LogLevel (${log.level}). Raw Message: ${fullMessage}`);
break;
}
},
getOpenDocumentByURL(url) {
return documents.get(url);
},
applyEdit(paramOrEdit) {
return connection.workspace.applyEdit(paramOrEdit);
},
};
const s = createServer(host);
server = s;
s.log({ level: `info`, message: `TypeSpec language server v${typespecVersion}` });
s.log({ level: `info`, message: `Module: ${fileURLToPath(import.meta.url)}` });
s.log({ level: `info`, message: `Process ID: ${process.pid}` });
s.log({ level: `info`, message: `Command Line`, detail: process.argv });
if (profileDir) {
s.log({ level: `info`, message: `CPU profiling enabled with dir: ${profileDir}` });
profileSession = new inspector.Session();
profileSession.connect();
}
connection.onInitialize(async (params) => {
if (params.capabilities.workspace?.workspaceFolders) {
clientHasWorkspaceFolderCapability = true;
}
return await s.initialize(params);
});
connection.onInitialized((params) => {
if (clientHasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders(s.workspaceFoldersChanged);
}
s.initialized(params);
});
connection.onDocumentFormatting(profile(s.formatDocument));
connection.onDidChangeWatchedFiles(profile(s.watchedFilesChanged));
connection.onDefinition(profile(s.gotoDefinition));
connection.onCompletion(profile(s.complete));
connection.onReferences(profile(s.findReferences));
connection.onRenameRequest(profile(s.rename));
connection.onPrepareRename(profile(s.prepareRename));
connection.onFoldingRanges(profile(s.getFoldingRanges));
connection.onDocumentSymbol(profile(s.getDocumentSymbols));
connection.onDocumentHighlight(profile(s.findDocumentHighlight));
connection.onHover(profile(s.getHover));
connection.onSignatureHelp(profile(s.getSignatureHelp));
connection.onCodeAction(profile(s.getCodeActions));
connection.onExecuteCommand(profile(s.executeCommand));
connection.languages.semanticTokens.on(profile(s.buildSemanticTokens));
connection.workspace.onDidRenameFiles(profile(s.renameFiles));
const validateInitProjectTemplate = "typespec/validateInitProjectTemplate";
connection.onRequest(validateInitProjectTemplate, profile(s.validateInitProjectTemplate));
const getInitProjectContextRequestName = "typespec/getInitProjectContext";
connection.onRequest(getInitProjectContextRequestName, profile(s.getInitProjectContext));
const initProjectRequestName = "typespec/initProject";
connection.onRequest(initProjectRequestName, profile(s.initProject));
const compileProjectRequestName = "typespec/internalCompile";
connection.onRequest(compileProjectRequestName, profile(s.internalCompile));
documents.onDidChangeContent(profile(s.checkChange));
documents.onDidClose(profile(s.documentClosed));
documents.listen(connection);
connection.listen();
}
function fatalError(e) {
// If we failed to send any log messages over LSP pipe, send them to
// stderr before exiting.
for (const pending of server?.pendingMessages ?? []) {
// eslint-disable-next-line no-console
console.error(pending);
}
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
}
function profile(func) {
const name = func.name;
if (logTiming) {
func = time(func);
}
if (!profileDir) {
return func;
}
return (async (...args) => {
profileSession.post("Profiler.enable", () => {
profileSession.post("Profiler.start", async () => {
const ret = await func.apply(undefined, args);
profileSession.post("Profiler.stop", async (err, args) => {
if (!err && args.profile) {
await mkdir(profileDir, { recursive: true });
await writeFile(join(profileDir, name + ".cpuprofile"), JSON.stringify(args.profile));
}
});
return ret;
});
});
});
}
function time(func) {
return (async (...args) => {
const start = Date.now();
const ret = await func.apply(undefined, args);
const end = Date.now();
server.log({ level: `trace`, message: `${func.name}: ${end - start + " ms"}` });
return ret;
});
}
//# sourceMappingURL=server.js.map