Last active
October 18, 2022 15:34
-
-
Save acutmore/e01a70838d689013d447114b5c15b4b9 to your computer and use it in GitHub Desktop.
A small wrapper around Typescript's tsserver for easier scripting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
const childProcess = require('child_process'); | |
const path = require('path'); | |
const TTL_MS = 5000; | |
/** | |
* @description Start a connection to tsserver | |
* @param {{debug?: boolean}} options | |
* @example | |
const chat = tsserverAPI.start(); | |
await chat.read(msg => msg.event === 'typingsInstallerPid'); | |
chat.send({ | |
type: 'request', | |
command: 'open', | |
arguments: { | |
file: '/path/to/file.ts' | |
} | |
}); | |
await chat.read(msg => msg.event === 'projectLoadingFinish'); | |
const reply = await chat.send({ | |
type: 'request', | |
command: 'implementation', | |
arguments: { | |
file: '/path/to/file.ts', | |
line: 6, | |
offset: 24 | |
} | |
}); | |
console.log(reply); | |
*/ | |
function start({debug} = {}) { | |
const tsserverPath = path.join(path.dirname(require.resolve('typescript')), 'tsserver.js'); | |
const proc = childProcess.fork(tsserverPath, [], {silent: true}); | |
const unreadMessages = new Set(); | |
const waitingReaders = new Set(); | |
let readBuffer = ''; | |
proc.stdout.on('data', buffer => { | |
readBuffer += buffer.toString('utf8'); | |
const lines = readBuffer.split('\n'); | |
readBuffer = ''; | |
for (const line of lines) { | |
if (line.startsWith('{')) { | |
try { | |
const msg = JSON.parse(line); | |
if (debug) { | |
console.debug('>>', msg); | |
} | |
unreadMessages.add(msg); | |
setTimeout(() => { | |
unreadMessages.delete(msg); | |
}, TTL_MS); | |
waitingReaders.forEach(asleepReader => { | |
asleepReader(); | |
}); | |
} catch { | |
readBuffer = line; | |
continue; | |
} | |
} | |
} | |
}); | |
let seq = 0; | |
/** | |
* @description Send 'msg' to the tsserver process | |
* @param {object} msg | |
* @returns {Promise<object>} The reponse from tsserver | |
*/ | |
async function send(msg) { | |
const msgSeq = (msg.seq = ++seq); | |
proc.stdin.write(JSON.stringify(msg) + '\n'); | |
return read(msg => msg.request_seq === msgSeq); | |
} | |
/** | |
* @description read the next message that matches 'condition' | |
* @param {Function} condition | |
* @returns {Promise<object>} the matched message | |
*/ | |
async function read(condition) { | |
return new Promise(resolve => { | |
function reader() { | |
for (const msg of unreadMessages.values()) { | |
if (condition(msg)) { | |
unreadMessages.delete(msg); | |
waitingReaders.delete(reader); | |
resolve(msg); | |
break; | |
} | |
} | |
} | |
reader(); | |
waitingReaders.add(reader); | |
}); | |
} | |
return { | |
send, | |
read | |
}; | |
} | |
module.exports = exports = { | |
start | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment