Writing a client for rust-analyzer

I am currently trying to embed rust-analyzer into Workbench.
It seems to work fine, but it only reports diagnostics for syntax errors like this one:

fn main() {
    1 +
}

Not type errors like this:

fn main() {
    1 + "";
}

I wrote a small Node.js script and could reproduce the behavior:

const { spawn } = require('child_process');
const path = require('path');

// Path to the Rust Analyzer executable and the file to analyze
const rustAnalyzerPath = 'rust-analyzer';
const filePath = path.resolve('src/main.rs');

// Start the Rust Analyzer server
const server = spawn(rustAnalyzerPath, [], { stdio: ['pipe', 'pipe', process.stderr] });

// Function to send a JSON-RPC message to the server
function sendMessage(method, params, id) {
    const message = JSON.stringify({ jsonrpc: '2.0', method, params, id });
    const contentLength = Buffer.byteLength(message, 'utf8');
    server.stdin.write(`Content-Length: ${contentLength}\r\n\r\n${message}`);
}

// Function to send a JSON-RPC request to the server
function sendRequest(method, params, id) {
    sendMessage(method, params, id);
}

// Function to send a JSON-RPC notification to the server
function sendNotification(method, params) {
    sendMessage(method, params, null);
}


// Initialize the LSP client
sendRequest('initialize', {
    processId: process.pid,
    rootPath: path.dirname(filePath),
    rootUri: `file://${path.dirname(filePath)}`,
    capabilities: {},
}, 1);

// Send the initialized notification
sendNotification('initialized', {});

// Open the file
sendNotification('textDocument/didOpen', {
    textDocument: {
        uri: `file://${filePath}`,
        languageId: 'rust',
        version: 1,
        text: require('fs').readFileSync(filePath, 'utf8'),
    },
});

// Read the server's responses
server.stdout.on('data', (data) => {
    console.log('Server response:', data.toString());
});

// Keep the Node.js process running
setInterval(() => {}, 1000);

I wonder if I am missing some configuration or if I need to call different procedures.
Any pointers are appreciated.

I believe in your initialize message you need to use the workspace directory as rootPath. Otherwise rust-analyzer can't find the cargo project for the source file and thus is unable to run cargo check to report errors. Rust-analyzer only has a small set of errors built in (mostly syntax errors), for the rest it depends on cargo check.

1 Like

Yes, I think you are right @bjorn3!
I've changed the rootPath to the following:

sendRequest('initialize', {
    processId: process.pid,
    rootPath: path.dirname(path.dirname(filePath)),
    capabilities: {},
}, 1);

Unfortunately, it doesn't change the behavior.

Rust-analyzer only looks at rootUri it seems: rust-analyzer/crates/rust-analyzer/src/bin/main.rs at 000ce5d29cc3d6801d1ddeb65aef46c9cb03d254 · rust-lang/rust-analyzer · GitHub

Still gives the same result unfortunately :slight_smile:

// Initialize the LSP client
sendRequest('initialize', {
    processId: process.pid,
    rootUri: `file://${path.dirname(path.dirname(filePath))}`,
    capabilities: {},
}, 1);

There seems to be a bug in rust-analyzer where if I do cargo clean and then try to run the latest version of the code you wrote with fn main() { 1; } and then again with fn main() { 1 + ""; } the previous result seems to be cached. Even cargo check will return a success result. I will open a bug report for this.

Opened

2 Likes

By the way, paths and file URLs are not identical in syntax. You should be URL-encoding the path components. Then test your code against a directory name that contains the "%" character to be sure it is working. (Though this is such a common confusion that other things you didn't write may break too.)

Thanks for the tip @kpreid I indeed wasn't aware of that