Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 132 additions & 4 deletions crates/oxc_language_server/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ impl Tool for FakeTool {
}
ToolRestartChanges { tool: None, diagnostic_reports: None, watch_patterns: None }
}

fn get_code_actions_or_commands(
&self,
uri: &Uri,
_range: &Range,
_only_code_action_kinds: Option<Vec<CodeActionKind>>,
) -> Vec<CodeActionOrCommand> {
if uri.as_str().ends_with("code_action.config") {
return vec![CodeActionOrCommand::CodeAction(CodeAction {
title: "Code Action title".to_string(),
kind: Some(CodeActionKind::QUICKFIX),
edit: Some(WorkspaceEdit::default()),
..Default::default()
})];
}

vec![]
}
}

// A test server that can send requests and receive responses.
Expand Down Expand Up @@ -375,9 +393,55 @@ fn did_change_configuration(new_config: Option<serde_json::Value>) -> Request {
.finish()
}

fn did_open(uri: &str, text: &str) -> Request {
let params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.parse().unwrap(),
language_id: "plaintext".to_string(),
version: 1,
text: text.to_string(),
},
};

Request::build("textDocument/didOpen").params(json!(params)).finish()
}

fn did_change(uri: &str, text: &str) -> Request {
let params = DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier { uri: uri.parse().unwrap(), version: 2 },
content_changes: vec![TextDocumentContentChangeEvent {
text: text.to_string(),
range: None,
range_length: None,
}],
};

Request::build("textDocument/didChange").params(json!(params)).finish()
}

fn did_close(uri: &str) -> Request {
let params = DidCloseTextDocumentParams {
text_document: TextDocumentIdentifier { uri: uri.parse().unwrap() },
};

Request::build("textDocument/didClose").params(json!(params)).finish()
}

fn code_action(id: i64, uri: &str) -> Request {
let params = CodeActionParams {
text_document: TextDocumentIdentifier { uri: uri.parse().unwrap() },
range: Range::default(),
context: CodeActionContext { diagnostics: vec![], only: None, trigger_kind: None },
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
};

Request::build("textDocument/codeAction").id(id).params(json!(params)).finish()
}

#[cfg(test)]
mod test_suite {
use serde_json::json;
use serde_json::{Value, json};
use tower_lsp_server::{
jsonrpc::{Id, Response},
lsp_types::{
Expand All @@ -390,9 +454,10 @@ mod test_suite {
backend::Backend,
tests::{
FAKE_COMMAND, FakeToolBuilder, TestServer, WORKSPACE, acknowledge_registrations,
acknowledge_unregistrations, did_change_configuration, did_change_watched_files,
execute_command_request, initialize_request, initialized_notification,
response_to_configuration, shutdown_request, workspace_folders_changed,
acknowledge_unregistrations, code_action, did_change, did_change_configuration,
did_change_watched_files, did_close, did_open, execute_command_request,
initialize_request, initialized_notification, response_to_configuration,
shutdown_request, workspace_folders_changed,
},
};

Expand Down Expand Up @@ -851,4 +916,67 @@ mod test_suite {

server.shutdown_with_watchers(3).await;
}

#[tokio::test]
async fn test_file_notifications() {
let mut server = TestServer::new_initialized(
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
initialize_request(false, false, false),
)
.await;

let file = format!("{WORKSPACE}/file.txt");

server.send_request(did_open(&file, "some text")).await;
server.send_request(did_change(&file, "changed text")).await;
server.send_request(did_close(&file)).await;
server.shutdown_with_watchers(3).await;
}

#[tokio::test]
async fn test_code_action_no_actions() {
let mut server = TestServer::new_initialized(
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
initialize_request(false, false, false),
)
.await;

let file = format!("{WORKSPACE}/file.txt");

server.send_request(did_open(&file, "some text")).await;

// No code actions expected
server.send_request(code_action(3, &file)).await;
let response = server.recv_response().await;
assert!(response.is_ok());
assert!(response.id() == &Id::Number(3));
assert!(response.result().is_some_and(|result| *result == Value::Null));

server.shutdown_with_watchers(4).await;
}

#[tokio::test]
async fn test_code_actions_with_actions() {
let mut server = TestServer::new_initialized(
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
initialize_request(false, false, false),
)
.await;

let file = format!("{WORKSPACE}/code_action.config");

server.send_request(did_open(&file, "some text")).await;

// Code actions expected
server.send_request(code_action(3, &file)).await;
let response = server.recv_response().await;
assert!(response.is_ok());
assert!(response.id() == &Id::Number(3));
let actions: Vec<serde_json::Value> =
serde_json::from_value(response.result().unwrap().clone()).unwrap();
assert_eq!(actions.len(), 1);
assert_eq!(actions[0]["title"], "Code Action title");

server.shutdown_with_watchers(4).await;
}
}
34 changes: 33 additions & 1 deletion crates/oxc_language_server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ fn registration_tool_watcher_id(tool: &str, root_uri: &Uri, patterns: Vec<String
mod tests {
use std::str::FromStr;

use tower_lsp_server::lsp_types::{FileChangeType, FileEvent, Uri};
use tower_lsp_server::lsp_types::{CodeActionOrCommand, FileChangeType, FileEvent, Range, Uri};

use crate::{
ToolBuilder,
Expand Down Expand Up @@ -531,4 +531,36 @@ mod tests {

// TODO: add test for tool replacement
}

#[tokio::test]
async fn test_code_action_collection() {
let worker = WorkspaceWorker::new(Uri::from_str("file:///root/").unwrap());
let tools: Vec<Box<dyn ToolBuilder>> = vec![Box::new(FakeToolBuilder)];
worker.start_worker(serde_json::Value::Null, &tools).await;

let actions = worker
.get_code_actions_or_commands(
&Uri::from_str("file:///root/file.js").unwrap(),
&Range::default(),
None,
)
.await;

assert_eq!(actions.len(), 0);

let actions = worker
.get_code_actions_or_commands(
&Uri::from_str("file:///root/code_action.config").unwrap(),
&Range::default(),
None,
)
.await;

assert_eq!(actions.len(), 1);
if let CodeActionOrCommand::CodeAction(action) = &actions[0] {
assert_eq!(action.title, "Code Action title");
} else {
panic!("Expected CodeAction");
}
}
}
Loading