corsa 0.7.0

Production-oriented Rust bindings, orchestration layers, and Node integration for typescript-go
Documentation
use crate::{Result, common, jsonrpc};
use base64::Engine as _;
use corsa::jsonrpc::{RawMessage, RequestId};
use serde_json::{Value, json};
use std::io::{BufReader, BufWriter};

pub fn run(cwd: String, callbacks: Vec<String>) -> Result<()> {
    let stdin = std::io::stdin();
    let stdout = std::io::stdout();
    let mut reader = BufReader::new(stdin.lock());
    let mut writer = BufWriter::new(stdout.lock());
    loop {
        let Some(message) = jsonrpc::read_message(&mut reader)? else {
            return Ok(());
        };
        let method = message.method.unwrap_or_default();
        let id = message.id.clone();
        let params = message.params.unwrap_or(Value::Null);
        let response = match method.as_str() {
            "initialize" => Some(json!({
                "useCaseSensitiveFileNames": true,
                "currentDirectory": cwd,
            })),
            "describeCapabilities" => Some(common::capabilities()),
            "parseConfigFile" => Some(parse_config(&mut reader, &mut writer, &callbacks, params)?),
            "updateSnapshot" => Some(common::snapshot_from_update_params(
                "/workspace/tsconfig.json",
                &params,
            )),
            "getDefaultProjectForFile" => Some(common::project("/workspace/tsconfig.json")),
            "getSourceFile" => Some(common::encoded(b"source-file")),
            "getDiagnosticsForSnapshot" => Some(common::snapshot_diagnostics(json!(
                "/workspace/src/index.ts"
            ))),
            "getDiagnosticsForProject" => Some(common::project_diagnostics(json!(
                "/workspace/src/index.ts"
            ))),
            "getDiagnosticsForFile" => Some(common::file_diagnostics(
                params
                    .get("file")
                    .cloned()
                    .unwrap_or_else(|| json!("/workspace/src/index.ts")),
            )),
            "getHoverAtPosition" => Some(common::hover()),
            "getDefinitionAtPosition" => Some(common::definition()),
            "getReferencesAtPosition" => Some(common::references()),
            "getRenameAtPosition" => Some(common::rename(
                params
                    .get("newName")
                    .and_then(Value::as_str)
                    .unwrap_or("renamedValue"),
            )),
            "getCompletionAtPosition" => Some(common::completion()),
            "getSymbolAtPosition" | "getSymbolAtLocation" | "resolveName" => {
                Some(common::symbol("value"))
            }
            "getSymbolsAtPositions" | "getSymbolsAtLocations" => {
                Some(json!([common::symbol("value"), Value::Null]))
            }
            "getTypeOfSymbol"
            | "getDeclaredTypeOfSymbol"
            | "getTypeAtLocation"
            | "getTypeAtPosition"
            | "getContextualType"
            | "getBaseTypeOfLiteralType"
            | "getTypeOfSymbolAtLocation"
            | "getTargetOfType"
            | "getObjectTypeOfType"
            | "getIndexTypeOfType"
            | "getCheckTypeOfType"
            | "getExtendsTypeOfType"
            | "getBaseTypeOfType"
            | "getConstraintOfType"
            | "getReturnTypeOfSignature"
            | "getRestTypeOfSignature"
            | "getConstraintOfTypeParameter" => Some(common::type_response("t0000000000000001")),
            "getTypesOfSymbols" | "getTypeAtLocations" | "getTypesAtPositions" => {
                let count = params
                    .as_object()
                    .and_then(|value| {
                        value
                            .get("symbols")
                            .or_else(|| value.get("locations"))
                            .or_else(|| value.get("positions"))
                            .and_then(Value::as_array)
                    })
                    .map(Vec::len)
                    .unwrap_or(1);
                Some(Value::Array(
                    (0..count)
                        .map(|_| common::type_response("t0000000000000001"))
                        .collect(),
                ))
            }
            "getBaseTypes"
            | "getTypeArguments"
            | "getTypesOfType"
            | "getTypeParametersOfType"
            | "getOuterTypeParametersOfType"
            | "getLocalTypeParametersOfType" => {
                Some(json!([common::type_response("t0000000000000001")]))
            }
            "getSignaturesOfType" => Some(json!([common::signature()])),
            "getShorthandAssignmentValueSymbol" | "getParentOfSymbol" | "getSymbolOfType" => {
                Some(common::symbol("value"))
            }
            "getMembersOfSymbol" | "getExportsOfSymbol" | "getPropertiesOfType" => {
                Some(json!([common::symbol("value")]))
            }
            "getExportSymbolOfSymbol" => Some(common::symbol("exported")),
            "getTypePredicateOfSignature" => Some(common::type_predicate()),
            "getIndexInfosOfType" => Some(json!([common::index_info()])),
            "getAnyType" | "getStringType" | "getNumberType" | "getBooleanType" | "getVoidType"
            | "getUndefinedType" | "getNullType" | "getNeverType" | "getUnknownType"
            | "getBigIntType" | "getESSymbolType" => {
                Some(common::type_response("t0000000000000010"))
            }
            "typeToTypeNode" => Some(common::encoded(b"type-node")),
            "typeToString" => Some(json!("type:string")),
            "isContextSensitive" => Some(json!(true)),
            "printNode" => Some(print_node(params)?),
            "release" => Some(Value::Null),
            "ping" => Some(json!("pong")),
            "echo" => Some(params),
            _ => None,
        };
        if let Some(id) = id {
            let response = response.unwrap_or(Value::Null);
            jsonrpc::write_message(&mut writer, &RawMessage::response(id, response))?;
        }
    }
}

fn parse_config<R: std::io::BufRead, W: std::io::Write>(
    reader: &mut R,
    writer: &mut W,
    callbacks: &[String],
    params: Value,
) -> Result<Value> {
    let file = params
        .get("file")
        .and_then(Value::as_str)
        .unwrap_or("/workspace/tsconfig.json");
    let mut options = json!({ "strict": true });
    if file.starts_with("/virtual/") && callbacks.iter().any(|name| name == "readFile") {
        let response = jsonrpc::send_request(
            reader,
            writer,
            RequestId::string("cb-readFile"),
            "readFile",
            Value::String(file.to_owned()),
        )?;
        options["virtual"] = json!(response.get("content").is_some());
    }
    Ok(json!({
        "options": options,
        "fileNames": ["/workspace/src/index.ts"],
    }))
}

fn print_node(params: Value) -> Result<Value> {
    let data = params
        .get("data")
        .and_then(Value::as_str)
        .unwrap_or_default();
    let decoded = base64::engine::general_purpose::STANDARD.decode(data)?;
    Ok(json!(format!(
        "print:{}",
        String::from_utf8_lossy(&decoded)
    )))
}