fake-lsp-server.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // Simple JSON-RPC 2.0 LSP-like fake server over stdio
  2. let nextId = 1
  3. let readBuffer = Buffer.alloc(0)
  4. let lastChange = null
  5. let initializeParams = null
  6. let diagnosticRequestCount = 0
  7. let registeredCapability = false
  8. const pendingClientRequests = new Map()
  9. let pullConfig = {
  10. delayMs: 0,
  11. registerOn: undefined,
  12. registrations: [],
  13. documentDiagnostics: [],
  14. documentDiagnosticsByIdentifier: {},
  15. documentDelayMsByIdentifier: {},
  16. workspaceDiagnostics: [],
  17. workspaceDiagnosticsByIdentifier: {},
  18. workspaceDelayMsByIdentifier: {},
  19. }
  20. function encode(message) {
  21. const json = JSON.stringify(message)
  22. const header = `Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n`
  23. return Buffer.concat([Buffer.from(header, "utf8"), Buffer.from(json, "utf8")])
  24. }
  25. function decodeFrames(buffer) {
  26. const results = []
  27. let idx
  28. while ((idx = buffer.indexOf("\r\n\r\n")) !== -1) {
  29. const header = buffer.slice(0, idx).toString("utf8")
  30. const match = /Content-Length:\s*(\d+)/i.exec(header)
  31. const length = match ? parseInt(match[1], 10) : 0
  32. const bodyStart = idx + 4
  33. const bodyEnd = bodyStart + length
  34. if (buffer.length < bodyEnd) break
  35. results.push(buffer.slice(bodyStart, bodyEnd).toString("utf8"))
  36. buffer = buffer.slice(bodyEnd)
  37. }
  38. return { messages: results, rest: buffer }
  39. }
  40. function send(message) {
  41. process.stdout.write(encode(message))
  42. }
  43. function sendRequest(method, params) {
  44. const id = nextId++
  45. send({ jsonrpc: "2.0", id, method, params })
  46. return id
  47. }
  48. function sendResponse(id, result) {
  49. send({ jsonrpc: "2.0", id, result })
  50. }
  51. function sendNotification(method, params) {
  52. send({ jsonrpc: "2.0", method, params })
  53. }
  54. function maybeRegister(method) {
  55. if (pullConfig.registerOn !== method || registeredCapability) return
  56. registeredCapability = true
  57. sendRequest("client/registerCapability", {
  58. registrations: pullConfig.registrations.map((registration, index) => ({
  59. id: registration.id ?? `pull-${index}`,
  60. method: registration.method ?? "textDocument/diagnostic",
  61. registerOptions: registration.registerOptions ?? registration,
  62. })),
  63. })
  64. }
  65. function delayed(id, result, delayMs = pullConfig.delayMs) {
  66. if (!delayMs) {
  67. sendResponse(id, result)
  68. return
  69. }
  70. setTimeout(() => sendResponse(id, result), delayMs)
  71. }
  72. function diagnosticsForIdentifier(identifier) {
  73. return pullConfig.documentDiagnosticsByIdentifier[identifier] ?? pullConfig.documentDiagnostics
  74. }
  75. function workspaceDiagnosticsForIdentifier(identifier) {
  76. return pullConfig.workspaceDiagnosticsByIdentifier[identifier] ?? pullConfig.workspaceDiagnostics
  77. }
  78. function documentDelayForIdentifier(identifier) {
  79. return pullConfig.documentDelayMsByIdentifier[identifier] ?? pullConfig.delayMs
  80. }
  81. function workspaceDelayForIdentifier(identifier) {
  82. return pullConfig.workspaceDelayMsByIdentifier[identifier] ?? pullConfig.delayMs
  83. }
  84. function handle(raw) {
  85. let data
  86. try {
  87. data = JSON.parse(raw)
  88. } catch {
  89. return
  90. }
  91. if (typeof data.method === "undefined" && typeof data.id !== "undefined") {
  92. const pending = pendingClientRequests.get(data.id)
  93. if (!pending) return
  94. pendingClientRequests.delete(data.id)
  95. sendResponse(pending, data.result ?? null)
  96. return
  97. }
  98. if (data.method === "initialize") {
  99. initializeParams = data.params
  100. sendResponse(data.id, {
  101. capabilities: {
  102. textDocumentSync: {
  103. change: 2,
  104. },
  105. },
  106. })
  107. return
  108. }
  109. if (data.method === "test/get-initialize-params") {
  110. sendResponse(data.id, initializeParams)
  111. return
  112. }
  113. if (data.method === "test/request-configuration") {
  114. const id = sendRequest("workspace/configuration", data.params)
  115. pendingClientRequests.set(id, data.id)
  116. return
  117. }
  118. if (data.method === "initialized" || data.method === "workspace/didChangeConfiguration") {
  119. return
  120. }
  121. if (data.method === "textDocument/didOpen") {
  122. maybeRegister("didOpen")
  123. return
  124. }
  125. if (data.method === "textDocument/didChange") {
  126. lastChange = data.params
  127. maybeRegister("didChange")
  128. return
  129. }
  130. if (data.method === "test/trigger") {
  131. const method = data.params && data.params.method
  132. if (method === "client/registerCapability") {
  133. sendRequest(method, {
  134. registrations: [
  135. {
  136. id: "test-diagnostic-registration",
  137. method: "textDocument/diagnostic",
  138. registerOptions: { identifier: "syntax" },
  139. },
  140. ],
  141. })
  142. return
  143. }
  144. if (method === "client/unregisterCapability") {
  145. sendRequest(method, {
  146. unregisterations: [{ id: "test-diagnostic-registration", method: "textDocument/diagnostic" }],
  147. })
  148. return
  149. }
  150. if (method) sendRequest(method, {})
  151. return
  152. }
  153. if (data.method === "test/configure-pull-diagnostics") {
  154. pullConfig = {
  155. delayMs: data.params?.delayMs ?? 0,
  156. registerOn: data.params?.registerOn,
  157. registrations: data.params?.registrations ?? [],
  158. documentDiagnostics: data.params?.documentDiagnostics ?? [],
  159. documentDiagnosticsByIdentifier: data.params?.documentDiagnosticsByIdentifier ?? {},
  160. documentDelayMsByIdentifier: data.params?.documentDelayMsByIdentifier ?? {},
  161. workspaceDiagnostics: data.params?.workspaceDiagnostics ?? [],
  162. workspaceDiagnosticsByIdentifier: data.params?.workspaceDiagnosticsByIdentifier ?? {},
  163. workspaceDelayMsByIdentifier: data.params?.workspaceDelayMsByIdentifier ?? {},
  164. }
  165. registeredCapability = false
  166. sendResponse(data.id, null)
  167. return
  168. }
  169. if (data.method === "test/register-configured-pull-diagnostics") {
  170. maybeRegister(undefined)
  171. sendResponse(data.id, null)
  172. return
  173. }
  174. if (data.method === "test/publish-diagnostics") {
  175. sendNotification("textDocument/publishDiagnostics", data.params)
  176. return
  177. }
  178. if (data.method === "test/get-last-change") {
  179. sendResponse(data.id, lastChange)
  180. return
  181. }
  182. if (data.method === "test/get-diagnostic-request-count") {
  183. sendResponse(data.id, diagnosticRequestCount)
  184. return
  185. }
  186. if (data.method === "textDocument/diagnostic") {
  187. diagnosticRequestCount += 1
  188. delayed(
  189. data.id,
  190. {
  191. kind: "full",
  192. items: diagnosticsForIdentifier(data.params?.identifier ?? ""),
  193. },
  194. documentDelayForIdentifier(data.params?.identifier ?? ""),
  195. )
  196. return
  197. }
  198. if (data.method === "workspace/diagnostic") {
  199. diagnosticRequestCount += 1
  200. delayed(
  201. data.id,
  202. {
  203. items: workspaceDiagnosticsForIdentifier(data.params?.identifier ?? ""),
  204. },
  205. workspaceDelayForIdentifier(data.params?.identifier ?? ""),
  206. )
  207. return
  208. }
  209. if (typeof data.id !== "undefined") {
  210. sendResponse(data.id, null)
  211. }
  212. }
  213. process.stdin.on("data", (chunk) => {
  214. readBuffer = Buffer.concat([readBuffer, chunk])
  215. const { messages, rest } = decodeFrames(readBuffer)
  216. readBuffer = rest
  217. for (const message of messages) handle(message)
  218. })