| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- // Simple JSON-RPC 2.0 LSP-like fake server over stdio
- let nextId = 1
- let readBuffer = Buffer.alloc(0)
- let lastChange = null
- let initializeParams = null
- let diagnosticRequestCount = 0
- let registeredCapability = false
- const pendingClientRequests = new Map()
- let pullConfig = {
- delayMs: 0,
- registerOn: undefined,
- registrations: [],
- documentDiagnostics: [],
- documentDiagnosticsByIdentifier: {},
- documentDelayMsByIdentifier: {},
- workspaceDiagnostics: [],
- workspaceDiagnosticsByIdentifier: {},
- workspaceDelayMsByIdentifier: {},
- }
- function encode(message) {
- const json = JSON.stringify(message)
- const header = `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n`
- return Buffer.concat([Buffer.from(header, "utf8"), Buffer.from(json, "utf8")])
- }
- function decodeFrames(buffer) {
- const results = []
- let idx
- while ((idx = buffer.indexOf("\r\n\r\n")) !== -1) {
- const header = buffer.slice(0, idx).toString("utf8")
- const match = /Content-Length:\s*(\d+)/i.exec(header)
- const length = match ? parseInt(match[1], 10) : 0
- const bodyStart = idx + 4
- const bodyEnd = bodyStart + length
- if (buffer.length < bodyEnd) break
- results.push(buffer.slice(bodyStart, bodyEnd).toString("utf8"))
- buffer = buffer.slice(bodyEnd)
- }
- return { messages: results, rest: buffer }
- }
- function send(message) {
- process.stdout.write(encode(message))
- }
- function sendRequest(method, params) {
- const id = nextId++
- send({ jsonrpc: "2.0", id, method, params })
- return id
- }
- function sendResponse(id, result) {
- send({ jsonrpc: "2.0", id, result })
- }
- function sendNotification(method, params) {
- send({ jsonrpc: "2.0", method, params })
- }
- function maybeRegister(method) {
- if (pullConfig.registerOn !== method || registeredCapability) return
- registeredCapability = true
- sendRequest("client/registerCapability", {
- registrations: pullConfig.registrations.map((registration, index) => ({
- id: registration.id ?? `pull-${index}`,
- method: registration.method ?? "textDocument/diagnostic",
- registerOptions: registration.registerOptions ?? registration,
- })),
- })
- }
- function delayed(id, result, delayMs = pullConfig.delayMs) {
- if (!delayMs) {
- sendResponse(id, result)
- return
- }
- setTimeout(() => sendResponse(id, result), delayMs)
- }
- function diagnosticsForIdentifier(identifier) {
- return pullConfig.documentDiagnosticsByIdentifier[identifier] ?? pullConfig.documentDiagnostics
- }
- function workspaceDiagnosticsForIdentifier(identifier) {
- return pullConfig.workspaceDiagnosticsByIdentifier[identifier] ?? pullConfig.workspaceDiagnostics
- }
- function documentDelayForIdentifier(identifier) {
- return pullConfig.documentDelayMsByIdentifier[identifier] ?? pullConfig.delayMs
- }
- function workspaceDelayForIdentifier(identifier) {
- return pullConfig.workspaceDelayMsByIdentifier[identifier] ?? pullConfig.delayMs
- }
- function handle(raw) {
- let data
- try {
- data = JSON.parse(raw)
- } catch {
- return
- }
- if (typeof data.method === "undefined" && typeof data.id !== "undefined") {
- const pending = pendingClientRequests.get(data.id)
- if (!pending) return
- pendingClientRequests.delete(data.id)
- sendResponse(pending, data.result ?? null)
- return
- }
- if (data.method === "initialize") {
- initializeParams = data.params
- sendResponse(data.id, {
- capabilities: {
- textDocumentSync: {
- change: 2,
- },
- },
- })
- return
- }
- if (data.method === "test/get-initialize-params") {
- sendResponse(data.id, initializeParams)
- return
- }
- if (data.method === "test/request-configuration") {
- const id = sendRequest("workspace/configuration", data.params)
- pendingClientRequests.set(id, data.id)
- return
- }
- if (data.method === "initialized" || data.method === "workspace/didChangeConfiguration") {
- return
- }
- if (data.method === "textDocument/didOpen") {
- maybeRegister("didOpen")
- return
- }
- if (data.method === "textDocument/didChange") {
- lastChange = data.params
- maybeRegister("didChange")
- return
- }
- if (data.method === "test/trigger") {
- const method = data.params && data.params.method
- if (method === "client/registerCapability") {
- sendRequest(method, {
- registrations: [
- {
- id: "test-diagnostic-registration",
- method: "textDocument/diagnostic",
- registerOptions: { identifier: "syntax" },
- },
- ],
- })
- return
- }
- if (method === "client/unregisterCapability") {
- sendRequest(method, {
- unregisterations: [{ id: "test-diagnostic-registration", method: "textDocument/diagnostic" }],
- })
- return
- }
- if (method) sendRequest(method, {})
- return
- }
- if (data.method === "test/configure-pull-diagnostics") {
- pullConfig = {
- delayMs: data.params?.delayMs ?? 0,
- registerOn: data.params?.registerOn,
- registrations: data.params?.registrations ?? [],
- documentDiagnostics: data.params?.documentDiagnostics ?? [],
- documentDiagnosticsByIdentifier: data.params?.documentDiagnosticsByIdentifier ?? {},
- documentDelayMsByIdentifier: data.params?.documentDelayMsByIdentifier ?? {},
- workspaceDiagnostics: data.params?.workspaceDiagnostics ?? [],
- workspaceDiagnosticsByIdentifier: data.params?.workspaceDiagnosticsByIdentifier ?? {},
- workspaceDelayMsByIdentifier: data.params?.workspaceDelayMsByIdentifier ?? {},
- }
- registeredCapability = false
- sendResponse(data.id, null)
- return
- }
- if (data.method === "test/register-configured-pull-diagnostics") {
- maybeRegister(undefined)
- sendResponse(data.id, null)
- return
- }
- if (data.method === "test/publish-diagnostics") {
- sendNotification("textDocument/publishDiagnostics", data.params)
- return
- }
- if (data.method === "test/get-last-change") {
- sendResponse(data.id, lastChange)
- return
- }
- if (data.method === "test/get-diagnostic-request-count") {
- sendResponse(data.id, diagnosticRequestCount)
- return
- }
- if (data.method === "textDocument/diagnostic") {
- diagnosticRequestCount += 1
- delayed(
- data.id,
- {
- kind: "full",
- items: diagnosticsForIdentifier(data.params?.identifier ?? ""),
- },
- documentDelayForIdentifier(data.params?.identifier ?? ""),
- )
- return
- }
- if (data.method === "workspace/diagnostic") {
- diagnosticRequestCount += 1
- delayed(
- data.id,
- {
- items: workspaceDiagnosticsForIdentifier(data.params?.identifier ?? ""),
- },
- workspaceDelayForIdentifier(data.params?.identifier ?? ""),
- )
- return
- }
- if (typeof data.id !== "undefined") {
- sendResponse(data.id, null)
- }
- }
- process.stdin.on("data", (chunk) => {
- readBuffer = Buffer.concat([readBuffer, chunk])
- const { messages, rest } = decodeFrames(readBuffer)
- readBuffer = rest
- for (const message of messages) handle(message)
- })
|