Skip to content

Commit 15c2922

Browse files
committed
refactor(language_server): introduce dummy ServerFormatter
1 parent dabd34c commit 15c2922

File tree

8 files changed

+100
-6
lines changed

8 files changed

+100
-6
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_language_server/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ doctest = false
2424
[dependencies]
2525
oxc_allocator = { workspace = true }
2626
oxc_diagnostics = { workspace = true }
27+
oxc_formatter = { workspace = true }
2728
oxc_linter = { workspace = true, features = ["language_server"] }
29+
oxc_parser = { workspace = true }
30+
oxc_span = { workspace = true }
2831

2932
#
3033
env_logger = { workspace = true, features = ["humantime"] }

crates/oxc_language_server/src/capabilities.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ impl From<Capabilities> for ServerCapabilities {
100100
} else {
101101
None
102102
},
103+
// ToDo: should by dynamic registered with workspace configuration
104+
// THIS MUST NOT BE COMMITTED TO MAIN!!!
105+
document_formatting_provider: Some(OneOf::Left(true)),
103106
..ServerCapabilities::default()
104107
}
105108
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod options;
2+
pub mod server_formatter;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use oxc_allocator::Allocator;
2+
use oxc_formatter::{FormatOptions, Formatter, get_supported_source_type};
3+
use oxc_parser::{ParseOptions, Parser};
4+
use tower_lsp_server::{
5+
UriExt,
6+
lsp_types::{Position, Range, TextEdit, Uri},
7+
};
8+
9+
use crate::LSP_MAX_INT;
10+
11+
pub struct ServerFormatter;
12+
13+
impl ServerFormatter {
14+
pub fn new() -> Self {
15+
Self {}
16+
}
17+
18+
#[expect(clippy::unused_self)]
19+
pub fn run_single(&self, uri: &Uri) -> Option<Vec<TextEdit>> {
20+
let path = uri.to_file_path()?;
21+
let source_type = get_supported_source_type(&path)?;
22+
let source_text = std::fs::read_to_string(path).expect("Failed to read file");
23+
24+
let allocator = Allocator::new();
25+
let ret = Parser::new(&allocator, &source_text, source_type)
26+
.with_options(ParseOptions {
27+
parse_regular_expression: false,
28+
// Enable all syntax features
29+
allow_v8_intrinsics: true,
30+
allow_return_outside_function: true,
31+
// `oxc_formatter` expects this to be false
32+
preserve_parens: false,
33+
})
34+
.parse();
35+
36+
if !ret.errors.is_empty() {
37+
return None;
38+
}
39+
40+
let options = FormatOptions {
41+
// semicolons: "always".parse().unwrap(),
42+
semicolons: "as-needed".parse().unwrap(),
43+
..FormatOptions::default()
44+
};
45+
let code = Formatter::new(&allocator, options).build(&ret.program);
46+
47+
Some(vec![TextEdit::new(
48+
Range::new(Position::new(0, 0), Position::new(LSP_MAX_INT, 0)),
49+
code,
50+
)])
51+
}
52+
}

crates/oxc_language_server/src/linter/error_with_position.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ use tower_lsp_server::lsp_types::{
77

88
use oxc_diagnostics::Severity;
99

10-
// max range for LSP integer is 2^31 - 1
11-
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#baseTypes
12-
const LSP_MAX_INT: u32 = 2u32.pow(31) - 1;
10+
use crate::LSP_MAX_INT;
1311

1412
#[derive(Debug, Clone)]
1513
pub struct DiagnosticReport {

crates/oxc_language_server/src/main.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use tower_lsp_server::{
1414
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
1515
DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersParams,
1616
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
17-
ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, Registration,
18-
ServerInfo, Unregistration, Uri, WorkspaceEdit,
17+
DocumentFormattingParams, ExecuteCommandParams, InitializeParams, InitializeResult,
18+
InitializedParams, Registration, ServerInfo, TextEdit, Unregistration, Uri, WorkspaceEdit,
1919
},
2020
};
2121

@@ -43,6 +43,10 @@ type ConcurrentHashMap<K, V> = papaya::HashMap<K, V, FxBuildHasher>;
4343

4444
const OXC_CONFIG_FILE: &str = ".oxlintrc.json";
4545

46+
// max range for LSP integer is 2^31 - 1
47+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#baseTypes
48+
const LSP_MAX_INT: u32 = 2u32.pow(31) - 1;
49+
4650
struct Backend {
4751
client: Client,
4852
// Each Workspace has it own worker with Linter (and in the future the formatter).
@@ -163,6 +167,8 @@ impl LanguageServer for Backend {
163167
if worker.needs_init_linter().await {
164168
needed_configurations.insert(worker.get_root_uri().clone(), worker);
165169
}
170+
// ToDo: check for configuration
171+
worker.init_formatter().await;
166172
}
167173

168174
if !needed_configurations.is_empty() {
@@ -590,6 +596,15 @@ impl LanguageServer for Backend {
590596

591597
Err(Error::invalid_request())
592598
}
599+
600+
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
601+
let uri = &params.text_document.uri;
602+
let workers = self.workspace_workers.read().await;
603+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
604+
return Ok(None);
605+
};
606+
Ok(worker.format_file(uri).await)
607+
}
593608
}
594609

595610
impl Backend {

crates/oxc_language_server/src/worker.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
apply_all_fix_code_action, apply_fix_code_actions, ignore_this_line_code_action,
1717
ignore_this_rule_code_action,
1818
},
19+
formatter::server_formatter::ServerFormatter,
1920
linter::{
2021
error_with_position::{DiagnosticReport, PossibleFixContent},
2122
server_linter::{ServerLinter, ServerLinterRun, normalize_path},
@@ -25,12 +26,18 @@ use crate::{
2526
pub struct WorkspaceWorker {
2627
root_uri: Uri,
2728
server_linter: RwLock<Option<ServerLinter>>,
29+
server_formatter: RwLock<Option<ServerFormatter>>,
2830
options: Mutex<Options>,
2931
}
3032

3133
impl WorkspaceWorker {
3234
pub fn new(root_uri: Uri) -> Self {
33-
Self { root_uri, server_linter: RwLock::new(None), options: Mutex::new(Options::default()) }
35+
Self {
36+
root_uri,
37+
server_linter: RwLock::new(None),
38+
server_formatter: RwLock::new(None),
39+
options: Mutex::new(Options::default()),
40+
}
3441
}
3542

3643
pub fn get_root_uri(&self) -> &Uri {
@@ -49,6 +56,10 @@ impl WorkspaceWorker {
4956
*self.server_linter.write().await = Some(ServerLinter::new(&self.root_uri, &options.lint));
5057
}
5158

59+
pub async fn init_formatter(&self) {
60+
*self.server_formatter.write().await = Some(ServerFormatter::new());
61+
}
62+
5263
// WARNING: start all programs (linter, formatter) before calling this function
5364
// each program can tell us customized file watcher patterns
5465
pub async fn init_watchers(&self) -> Vec<FileSystemWatcher> {
@@ -134,6 +145,14 @@ impl WorkspaceWorker {
134145
server_linter.run_single(uri, content, run_type).await
135146
}
136147

148+
pub async fn format_file(&self, uri: &Uri) -> Option<Vec<TextEdit>> {
149+
let Some(server_formatter) = &*self.server_formatter.read().await else {
150+
return None;
151+
};
152+
153+
server_formatter.run_single(uri)
154+
}
155+
137156
async fn revalidate_diagnostics(
138157
&self,
139158
uris: Vec<Uri>,

0 commit comments

Comments
 (0)