use serde_json::{Value, json};
fn text_document_position(uri: &str, line: u32, character: u32) -> Value {
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character }
})
}
fn folder_array(folders: &[(&str, &str)]) -> Vec<Value> {
folders
.iter()
.map(|(uri, name)| json!({ "uri": uri, "name": name }))
.collect()
}
#[must_use]
pub fn initialize(
pid: u32,
roots: &[(&str, &str)],
initialization_options: Option<&Value>,
) -> Value {
let workspace_folders = folder_array(roots);
let root_uri = roots.first().map_or(Value::Null, |(uri, _)| json!(uri));
let mut params = json!({
"processId": pid,
"rootUri": root_uri,
"capabilities": {
"general": {
"positionEncodings": ["utf-8", "utf-16"]
},
"textDocument": {
"synchronization": {
"didSave": true,
"dynamicRegistration": false,
"willSave": false,
"willSaveWaitUntil": false
},
"publishDiagnostics": {
"versionSupport": true
},
"definition": {
"dynamicRegistration": false,
"linkSupport": true
},
"typeDefinition": {
"dynamicRegistration": false,
"linkSupport": true
},
"implementation": {
"dynamicRegistration": false,
"linkSupport": true
},
"declaration": {
"dynamicRegistration": false,
"linkSupport": true
},
"references": {
"dynamicRegistration": false
},
"documentSymbol": {
"dynamicRegistration": false,
"hierarchicalDocumentSymbolSupport": true
},
"callHierarchy": {
"dynamicRegistration": false
},
"typeHierarchy": {
"dynamicRegistration": false
},
"codeAction": {
"dynamicRegistration": false
},
"diagnostic": {
"dynamicRegistration": false
}
},
"workspace": {
"symbol": {
"resolveSupport": {
"properties": ["location.range"]
}
},
"workspaceFolders": true,
"configuration": true,
"didChangeConfiguration": {
"dynamicRegistration": false
}
},
"window": {
"workDoneProgress": true
}
},
"workspaceFolders": workspace_folders
});
if let Some(opts) = initialization_options {
params["initializationOptions"] = opts.clone();
}
params
}
#[must_use]
pub fn did_open(uri: &str, language_id: &str, version: i32, text: &str) -> Value {
json!({
"textDocument": {
"uri": uri,
"languageId": language_id,
"version": version,
"text": text
}
})
}
#[must_use]
pub fn did_change(uri: &str, version: i32, text: &str) -> Value {
json!({
"textDocument": { "uri": uri, "version": version },
"contentChanges": [{ "text": text }]
})
}
#[must_use]
pub fn did_close(uri: &str) -> Value {
json!({
"textDocument": { "uri": uri }
})
}
#[must_use]
pub fn did_save(uri: &str) -> Value {
json!({
"textDocument": { "uri": uri }
})
}
#[must_use]
pub fn did_change_workspace_folders(added: &[(&str, &str)], removed: &[(&str, &str)]) -> Value {
json!({
"event": {
"added": folder_array(added),
"removed": folder_array(removed)
}
})
}
#[must_use]
pub fn workspace_symbols(query: &str) -> Value {
json!({ "query": query })
}
#[must_use]
pub fn hover(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn definition(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn type_definition(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn implementation(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn prepare_rename(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn references(uri: &str, line: u32, character: u32, include_declaration: bool) -> Value {
json!({
"textDocument": { "uri": uri },
"position": { "line": line, "character": character },
"context": { "includeDeclaration": include_declaration }
})
}
#[must_use]
pub fn document_symbols(uri: &str) -> Value {
json!({ "textDocument": { "uri": uri } })
}
#[must_use]
pub fn prepare_call_hierarchy(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn incoming_calls(item: &Value) -> Value {
json!({ "item": item })
}
#[must_use]
pub fn outgoing_calls(item: &Value) -> Value {
json!({ "item": item })
}
#[must_use]
pub fn prepare_type_hierarchy(uri: &str, line: u32, character: u32) -> Value {
text_document_position(uri, line, character)
}
#[must_use]
pub fn supertypes(item: &Value) -> Value {
json!({ "item": item })
}
#[must_use]
pub fn text_document_diagnostic(uri: &str) -> Value {
json!({
"textDocument": { "uri": uri }
})
}
#[must_use]
pub fn subtypes(item: &Value) -> Value {
json!({ "item": item })
}
#[cfg(test)]
#[allow(
clippy::expect_used,
reason = "tests use expect for readable assertions"
)]
mod tests {
use super::*;
#[test]
fn initialize_single_root() {
let ours = initialize(42, &[("file:///workspace", "workspace")], None);
let expected = json!({
"processId": 42,
"rootUri": "file:///workspace",
"capabilities": {
"general": {
"positionEncodings": ["utf-8", "utf-16"]
},
"textDocument": {
"synchronization": {
"didSave": true,
"dynamicRegistration": false,
"willSave": false,
"willSaveWaitUntil": false
},
"publishDiagnostics": {
"versionSupport": true
},
"definition": {
"dynamicRegistration": false,
"linkSupport": true
},
"typeDefinition": {
"dynamicRegistration": false,
"linkSupport": true
},
"implementation": {
"dynamicRegistration": false,
"linkSupport": true
},
"declaration": {
"dynamicRegistration": false,
"linkSupport": true
},
"references": {
"dynamicRegistration": false
},
"documentSymbol": {
"dynamicRegistration": false,
"hierarchicalDocumentSymbolSupport": true
},
"callHierarchy": {
"dynamicRegistration": false
},
"typeHierarchy": {
"dynamicRegistration": false
},
"codeAction": {
"dynamicRegistration": false
},
"diagnostic": {
"dynamicRegistration": false
}
},
"workspace": {
"symbol": {
"resolveSupport": {
"properties": ["location.range"]
}
},
"workspaceFolders": true,
"configuration": true,
"didChangeConfiguration": {
"dynamicRegistration": false
}
},
"window": {
"workDoneProgress": true
}
},
"workspaceFolders": [
{ "uri": "file:///workspace", "name": "workspace" }
]
});
assert_eq!(ours, expected);
}
#[test]
fn initialize_capabilities_advertise_did_change_configuration() {
let ours = initialize(1, &[("file:///ws", "ws")], None);
let dcc = &ours["capabilities"]["workspace"]["didChangeConfiguration"];
assert_eq!(dcc["dynamicRegistration"], json!(false));
}
#[test]
fn initialize_with_options() {
let opts = json!({"key": "value"});
let ours = initialize(1, &[("file:///ws", "ws")], Some(&opts));
assert_eq!(ours["processId"], 1);
assert_eq!(ours["rootUri"], "file:///ws");
assert_eq!(ours["initializationOptions"], json!({"key": "value"}));
assert_eq!(
ours["workspaceFolders"],
json!([{"uri": "file:///ws", "name": "ws"}])
);
}
#[test]
fn did_open_golden() {
let ours = did_open("file:///foo.rs", "rust", 1, "fn main() {}");
assert_eq!(
ours,
json!({
"textDocument": {
"uri": "file:///foo.rs",
"languageId": "rust",
"version": 1,
"text": "fn main() {}"
}
})
);
}
#[test]
fn did_change_golden() {
let ours = did_change("file:///foo.rs", 2, "fn main() { println!() }");
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs", "version": 2 },
"contentChanges": [{ "text": "fn main() { println!() }" }]
})
);
}
#[test]
fn did_close_golden() {
let ours = did_close("file:///foo.rs");
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" }
})
);
}
#[test]
fn did_save_golden() {
let ours = did_save("file:///foo.rs");
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" }
})
);
}
#[test]
fn did_change_workspace_folders_golden() {
let ours =
did_change_workspace_folders(&[("file:///new", "new")], &[("file:///old", "old")]);
assert_eq!(
ours,
json!({
"event": {
"added": [{ "uri": "file:///new", "name": "new" }],
"removed": [{ "uri": "file:///old", "name": "old" }]
}
})
);
}
#[test]
fn workspace_symbols_golden() {
let ours = workspace_symbols("MyStruct");
assert_eq!(ours, json!({ "query": "MyStruct" }));
}
#[test]
fn hover_golden() {
let ours = hover("file:///foo.rs", 10, 5);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 10, "character": 5 }
})
);
}
#[test]
fn definition_golden() {
let ours = definition("file:///foo.rs", 10, 5);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 10, "character": 5 }
})
);
}
#[test]
fn type_definition_golden() {
let ours = type_definition("file:///foo.rs", 3, 8);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 3, "character": 8 }
})
);
}
#[test]
fn implementation_golden() {
let ours = implementation("file:///foo.rs", 5, 12);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 5, "character": 12 }
})
);
}
#[test]
fn prepare_rename_golden() {
let ours = prepare_rename("file:///foo.rs", 7, 4);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 7, "character": 4 }
})
);
}
#[test]
fn references_golden() {
let ours = references("file:///foo.rs", 10, 5, true);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 10, "character": 5 },
"context": { "includeDeclaration": true }
})
);
}
#[test]
fn document_symbols_golden() {
let ours = document_symbols("file:///foo.rs");
assert_eq!(ours, json!({ "textDocument": { "uri": "file:///foo.rs" } }));
}
fn sample_call_hierarchy_item() -> Value {
json!({
"name": "foo",
"kind": 12,
"uri": "file:///foo.rs",
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 1, "character": 0 }
},
"selectionRange": {
"start": { "line": 0, "character": 3 },
"end": { "line": 0, "character": 6 }
}
})
}
#[test]
fn prepare_call_hierarchy_golden() {
let ours = prepare_call_hierarchy("file:///foo.rs", 5, 3);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 5, "character": 3 }
})
);
}
#[test]
fn incoming_calls_golden() {
let item = sample_call_hierarchy_item();
let ours = incoming_calls(&item);
assert_eq!(ours, json!({ "item": item }));
}
#[test]
fn outgoing_calls_golden() {
let item = sample_call_hierarchy_item();
let ours = outgoing_calls(&item);
assert_eq!(ours, json!({ "item": item }));
}
#[test]
fn text_document_diagnostic_golden() {
let ours = text_document_diagnostic("file:///foo.rs");
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" }
})
);
}
fn sample_type_hierarchy_item() -> Value {
json!({
"name": "MyTrait",
"kind": 11,
"uri": "file:///foo.rs",
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 5, "character": 0 }
},
"selectionRange": {
"start": { "line": 0, "character": 6 },
"end": { "line": 0, "character": 13 }
}
})
}
#[test]
fn prepare_type_hierarchy_golden() {
let ours = prepare_type_hierarchy("file:///foo.rs", 2, 10);
assert_eq!(
ours,
json!({
"textDocument": { "uri": "file:///foo.rs" },
"position": { "line": 2, "character": 10 }
})
);
}
#[test]
fn supertypes_golden() {
let item = sample_type_hierarchy_item();
let ours = supertypes(&item);
assert_eq!(ours, json!({ "item": item }));
}
#[test]
fn subtypes_golden() {
let item = sample_type_hierarchy_item();
let ours = subtypes(&item);
assert_eq!(ours, json!({ "item": item }));
}
}