Skip to content

Commit 96843a8

Browse files
committed
test(lsp): initializationOptions test (#16544)
> This PR adds comprehensive test coverage for LSP initializationOptions handling > Implemented a custom test/configuration LSP method in the test infrastructure to introspect workspace worker configuration state
1 parent f7751cc commit 96843a8

File tree

3 files changed

+124
-35
lines changed

3 files changed

+124
-35
lines changed

crates/oxc_language_server/src/backend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub struct Backend {
5959
// WorkspaceWorkers are only written on 2 occasions:
6060
// 1. `initialize` request with workspace folders
6161
// 2. `workspace/didChangeWorkspaceFolders` request
62-
workspace_workers: Arc<RwLock<Vec<WorkspaceWorker>>>,
62+
pub(crate) workspace_workers: Arc<RwLock<Vec<WorkspaceWorker>>>,
6363
// Capabilities of the language server, set once during `initialize` request.
6464
// Depending on the client capabilities, the server supports different capabilities.
6565
capabilities: OnceCell<Capabilities>,

crates/oxc_language_server/src/tests.rs

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use std::collections::VecDeque;
22

3-
use serde_json::json;
3+
use serde_json::{Value, json};
44
use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream};
55
use tower_lsp_server::{
6-
Client, LanguageServer, LspService, Server,
6+
Client, LspService, Server,
77
jsonrpc::{ErrorCode, Id, Request, Response},
88
lsp_types::*,
99
};
1010

11-
use crate::{Tool, ToolBuilder, ToolRestartChanges};
11+
use crate::{Tool, ToolBuilder, ToolRestartChanges, backend::Backend};
1212

1313
pub struct FakeToolBuilder;
1414

@@ -159,15 +159,27 @@ struct TestServer {
159159
}
160160

161161
impl TestServer {
162-
fn new<F, S>(init: F) -> Self
162+
fn new<F>(init: F) -> Self
163163
where
164-
F: FnOnce(Client) -> S,
165-
S: LanguageServer,
164+
F: FnOnce(Client) -> Backend,
166165
{
166+
async fn test_configuration_handler(
167+
service: &Backend,
168+
_params: Value,
169+
) -> Result<Value, tower_lsp_server::jsonrpc::Error> {
170+
let mut configs = vec![];
171+
for worker in &*service.workspace_workers.read().await {
172+
configs.push(worker.options.lock().await.clone());
173+
}
174+
Ok(json!(configs))
175+
}
176+
167177
let (req_client, req_server) = tokio::io::duplex(1024);
168178
let (res_server, res_client) = tokio::io::duplex(1024);
169179

170-
let (service, socket) = LspService::new(init);
180+
let (service, socket) = LspService::build(init)
181+
.custom_method("test/configuration", test_configuration_handler)
182+
.finish();
171183

172184
tokio::spawn(Server::new(req_server, res_server, socket).serve(service));
173185

@@ -243,10 +255,9 @@ impl TestServer {
243255

244256
/// Creates a new TestServer and performs the initialize and initialized sequence.
245257
/// The `init` closure is used to create the LanguageServer instance.
246-
async fn new_initialized<F, S>(init: F, initialize: Request) -> Self
258+
async fn new_initialized<F>(init: F, initialize: Request) -> Self
247259
where
248-
F: FnOnce(Client) -> S,
249-
S: LanguageServer,
260+
F: FnOnce(Client) -> Backend,
250261
{
251262
let mut server = Self::new(init);
252263
let initialize_id = initialize.id().cloned();
@@ -288,6 +299,7 @@ fn initialize_request(
288299
workspace_configuration: bool,
289300
dynamic_watchers: bool,
290301
workspace_edit: bool,
302+
initialization_options: Option<Value>,
291303
) -> Request {
292304
let params = InitializeParams {
293305
workspace_folders: Some(vec![WorkspaceFolder {
@@ -306,6 +318,9 @@ fn initialize_request(
306318
}),
307319
..Default::default()
308320
},
321+
initialization_options,
322+
#[expect(deprecated)]
323+
root_uri: Some(WORKSPACE.parse().unwrap()),
309324
..Default::default()
310325
};
311326

@@ -448,6 +463,10 @@ fn code_action(id: i64, uri: &str) -> Request {
448463
Request::build("textDocument/codeAction").id(id).params(json!(params)).finish()
449464
}
450465

466+
fn test_configuration_request(id: i64) -> Request {
467+
Request::build("test/configuration").id(id).params(json!(null)).finish()
468+
}
469+
451470
#[cfg(test)]
452471
mod test_suite {
453472
use serde_json::{Value, json};
@@ -466,7 +485,7 @@ mod test_suite {
466485
acknowledge_unregistrations, code_action, did_change, did_change_configuration,
467486
did_change_watched_files, did_close, did_open, did_save, execute_command_request,
468487
initialize_request, initialized_notification, response_to_configuration,
469-
shutdown_request, workspace_folders_changed,
488+
shutdown_request, test_configuration_request, workspace_folders_changed,
470489
},
471490
};
472491

@@ -478,7 +497,7 @@ mod test_suite {
478497
async fn test_basic_start_and_shutdown_flow() {
479498
let mut server = TestServer::new(|client| Backend::new(client, server_info(), vec![]));
480499
// initialize request
481-
server.send_request(initialize_request(false, false, false)).await;
500+
server.send_request(initialize_request(false, false, false, None)).await;
482501
let initialize_result = server.recv_response().await;
483502

484503
assert!(initialize_result.is_ok());
@@ -502,11 +521,81 @@ mod test_suite {
502521
// is handled by the lsp service itself
503522
}
504523

524+
#[tokio::test]
525+
async fn test_initialize_with_options() {
526+
let init_options = json!([
527+
{
528+
"workspaceUri": WORKSPACE,
529+
"options": {
530+
"run": true,
531+
"configPath": "./custom.json",
532+
"fmt.experimental": true
533+
}
534+
}
535+
]);
536+
537+
let mut server = TestServer::new_initialized(
538+
|client| Backend::new(client, server_info(), vec![]),
539+
initialize_request(false, false, false, Some(init_options.clone())),
540+
)
541+
.await;
542+
543+
server.send_request(test_configuration_request(2)).await;
544+
let config_response = server.recv_response().await;
545+
546+
assert!(config_response.is_ok());
547+
assert_eq!(config_response.id(), &Id::Number(2));
548+
assert_eq!(
549+
*config_response.result().unwrap(),
550+
json!([{
551+
"run": true,
552+
"configPath": "./custom.json",
553+
"fmt.experimental": true
554+
}])
555+
);
556+
557+
// shutdown request
558+
server.shutdown(3).await;
559+
}
560+
561+
#[tokio::test]
562+
async fn test_initialize_with_deprecated_options() {
563+
let init_options = json!({
564+
"settings": {
565+
"run": true,
566+
"configPath": "./custom.json",
567+
"fmt.experimental": true
568+
}
569+
});
570+
571+
let mut server = TestServer::new_initialized(
572+
|client| Backend::new(client, server_info(), vec![]),
573+
initialize_request(false, false, false, Some(init_options)),
574+
)
575+
.await;
576+
577+
server.send_request(test_configuration_request(2)).await;
578+
let config_response = server.recv_response().await;
579+
580+
assert!(config_response.is_ok());
581+
assert_eq!(config_response.id(), &Id::Number(2));
582+
assert_eq!(
583+
*config_response.result().unwrap(),
584+
json!([{
585+
"run": true,
586+
"configPath": "./custom.json",
587+
"fmt.experimental": true
588+
}])
589+
);
590+
// shutdown request
591+
server.shutdown(3).await;
592+
}
593+
505594
#[tokio::test]
506595
async fn test_workspace_configuration_on_initialized() {
507596
let mut server = TestServer::new_initialized(
508597
|client| Backend::new(client, server_info(), vec![]),
509-
initialize_request(true, false, false),
598+
initialize_request(true, false, false, None),
510599
)
511600
.await;
512601

@@ -544,7 +633,7 @@ mod test_suite {
544633
async fn test_dynamic_watched_files_registration() {
545634
let mut server = TestServer::new_initialized(
546635
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
547-
initialize_request(false, true, false),
636+
initialize_request(false, true, false, None),
548637
)
549638
.await;
550639

@@ -610,7 +699,7 @@ mod test_suite {
610699
async fn test_execute_workspace_command_with_apply_edit() {
611700
let mut server = TestServer::new_initialized(
612701
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
613-
initialize_request(false, false, true),
702+
initialize_request(false, false, true, None),
614703
)
615704
.await;
616705

@@ -656,7 +745,7 @@ mod test_suite {
656745
async fn test_execute_workspace_command_with_no_edit() {
657746
let mut server = TestServer::new_initialized(
658747
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
659-
initialize_request(false, false, false),
748+
initialize_request(false, false, false, None),
660749
)
661750
.await;
662751

@@ -678,7 +767,7 @@ mod test_suite {
678767
async fn test_execute_workspace_command_with_invalid_command() {
679768
let mut server = TestServer::new_initialized(
680769
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
681-
initialize_request(false, false, false),
770+
initialize_request(false, false, false, None),
682771
)
683772
.await;
684773

@@ -708,7 +797,7 @@ mod test_suite {
708797

709798
let mut server = TestServer::new_initialized(
710799
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
711-
initialize_request(false, false, false),
800+
initialize_request(false, false, false, None),
712801
)
713802
.await;
714803
server.send_request(folders_changed_notification).await;
@@ -730,7 +819,7 @@ mod test_suite {
730819

731820
let mut server = TestServer::new_initialized(
732821
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
733-
initialize_request(false, true, false),
822+
initialize_request(false, true, false, None),
734823
)
735824
.await;
736825
acknowledge_registrations(&mut server).await;
@@ -754,7 +843,7 @@ mod test_suite {
754843

755844
let mut server = TestServer::new_initialized(
756845
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
757-
initialize_request(true, false, false),
846+
initialize_request(true, false, false, None),
758847
)
759848
.await;
760849
// workspace configuration request expected, one for initial workspace
@@ -781,7 +870,7 @@ mod test_suite {
781870

782871
let mut server = TestServer::new_initialized(
783872
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
784-
initialize_request(false, false, false),
873+
initialize_request(false, false, false, None),
785874
)
786875
.await;
787876
server.send_request(folders_changed_notification).await;
@@ -803,7 +892,7 @@ mod test_suite {
803892

804893
let mut server = TestServer::new_initialized(
805894
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
806-
initialize_request(false, true, false),
895+
initialize_request(false, true, false, None),
807896
)
808897
.await;
809898
acknowledge_registrations(&mut server).await;
@@ -819,7 +908,7 @@ mod test_suite {
819908
async fn test_watched_file_changed_unknown() {
820909
let mut server = TestServer::new_initialized(
821910
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
822-
initialize_request(false, true, false),
911+
initialize_request(false, true, false, None),
823912
)
824913
.await;
825914
acknowledge_registrations(&mut server).await;
@@ -839,7 +928,7 @@ mod test_suite {
839928
async fn test_watched_file_changed_new_watchers() {
840929
let mut server = TestServer::new_initialized(
841930
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
842-
initialize_request(false, true, false),
931+
initialize_request(false, true, false, None),
843932
)
844933
.await;
845934
acknowledge_registrations(&mut server).await;
@@ -861,7 +950,7 @@ mod test_suite {
861950
async fn test_did_change_configuration_no_changes() {
862951
let mut server = TestServer::new_initialized(
863952
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
864-
initialize_request(false, true, false),
953+
initialize_request(false, true, false, None),
865954
)
866955
.await;
867956
acknowledge_registrations(&mut server).await;
@@ -879,7 +968,7 @@ mod test_suite {
879968
async fn test_did_change_configuration_config_passed_new_watchers() {
880969
let mut server = TestServer::new_initialized(
881970
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
882-
initialize_request(false, true, false),
971+
initialize_request(false, true, false, None),
883972
)
884973
.await;
885974
acknowledge_registrations(&mut server).await;
@@ -905,7 +994,7 @@ mod test_suite {
905994
async fn test_did_change_configuration_config_requested_new_watchers() {
906995
let mut server = TestServer::new_initialized(
907996
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
908-
initialize_request(true, true, false),
997+
initialize_request(true, true, false, None),
909998
)
910999
.await;
9111000
response_to_configuration(&mut server, vec![json!(null)]).await;
@@ -930,7 +1019,7 @@ mod test_suite {
9301019
async fn test_file_notifications() {
9311020
let mut server = TestServer::new_initialized(
9321021
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
933-
initialize_request(false, false, false),
1022+
initialize_request(false, false, false, None),
9341023
)
9351024
.await;
9361025

@@ -947,7 +1036,7 @@ mod test_suite {
9471036
async fn test_code_action_no_actions() {
9481037
let mut server = TestServer::new_initialized(
9491038
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
950-
initialize_request(false, false, false),
1039+
initialize_request(false, false, false, None),
9511040
)
9521041
.await;
9531042

@@ -969,7 +1058,7 @@ mod test_suite {
9691058
async fn test_code_actions_with_actions() {
9701059
let mut server = TestServer::new_initialized(
9711060
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
972-
initialize_request(false, false, false),
1061+
initialize_request(false, false, false, None),
9731062
)
9741063
.await;
9751064

@@ -994,7 +1083,7 @@ mod test_suite {
9941083
async fn test_diagnostic_on_open() {
9951084
let mut server = TestServer::new_initialized(
9961085
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
997-
initialize_request(false, false, false),
1086+
initialize_request(false, false, false, None),
9981087
)
9991088
.await;
10001089

@@ -1020,7 +1109,7 @@ mod test_suite {
10201109
async fn test_diagnostic_on_change() {
10211110
let mut server = TestServer::new_initialized(
10221111
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
1023-
initialize_request(false, false, false),
1112+
initialize_request(false, false, false, None),
10241113
)
10251114
.await;
10261115

@@ -1050,7 +1139,7 @@ mod test_suite {
10501139
async fn test_diagnostic_on_save() {
10511140
let mut server = TestServer::new_initialized(
10521141
|client| Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder)]),
1053-
initialize_request(false, false, false),
1142+
initialize_request(false, false, false, None),
10541143
)
10551144
.await;
10561145

crates/oxc_language_server/src/worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub struct WorkspaceWorker {
2727
tools: RwLock<Vec<Box<dyn Tool>>>,
2828
// Initialized options from the client
2929
// If None, the worker has not been initialized yet
30-
options: Mutex<Option<serde_json::Value>>,
30+
pub(crate) options: Mutex<Option<serde_json::Value>>,
3131
}
3232

3333
impl WorkspaceWorker {

0 commit comments

Comments
 (0)