Skip to content

Commit 8ca332f

Browse files
committed
refactor(language_server): introduce dummy ServerFormatter
1 parent 3798dad commit 8ca332f

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
@@ -92,6 +92,9 @@ impl From<Capabilities> for ServerCapabilities {
9292
} else {
9393
None
9494
},
95+
// ToDo: should by dynamic registered with workspace configuration
96+
// THIS MUST NOT BE COMMITTED TO MAIN!!!
97+
document_formatting_provider: Some(OneOf::Left(true)),
9598
..ServerCapabilities::default()
9699
}
97100
}
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

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

4141
const OXC_CONFIG_FILE: &str = ".oxlintrc.json";
4242

43+
// max range for LSP integer is 2^31 - 1
44+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#baseTypes
45+
const LSP_MAX_INT: u32 = 2u32.pow(31) - 1;
46+
4347
struct Backend {
4448
client: Client,
4549
// Each Workspace has it own worker with Linter (and in the future the formatter).
@@ -159,6 +163,8 @@ impl LanguageServer for Backend {
159163
if worker.needs_init_linter().await {
160164
needed_configurations.insert(worker.get_root_uri().clone(), worker);
161165
}
166+
// ToDo: check for configuration
167+
worker.init_formatter().await;
162168
}
163169

164170
if !needed_configurations.is_empty() {
@@ -562,6 +568,15 @@ impl LanguageServer for Backend {
562568

563569
Err(Error::invalid_request())
564570
}
571+
572+
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
573+
let uri = &params.text_document.uri;
574+
let workers = self.workspace_workers.read().await;
575+
let Some(worker) = workers.iter().find(|worker| worker.is_responsible_for_uri(uri)) else {
576+
return Ok(None);
577+
};
578+
Ok(worker.format_file(uri).await)
579+
}
565580
}
566581

567582
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
options::LintOptions,
@@ -26,12 +27,18 @@ use crate::{
2627
pub struct WorkspaceWorker {
2728
root_uri: Uri,
2829
server_linter: RwLock<Option<ServerLinter>>,
30+
server_formatter: RwLock<Option<ServerFormatter>>,
2931
options: Mutex<Options>,
3032
}
3133

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

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

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

159+
pub async fn format_file(&self, uri: &Uri) -> Option<Vec<TextEdit>> {
160+
let Some(server_formatter) = &*self.server_formatter.read().await else {
161+
return None;
162+
};
163+
164+
server_formatter.run_single(uri)
165+
}
166+
148167
async fn revalidate_diagnostics(
149168
&self,
150169
uris: Vec<Uri>,

0 commit comments

Comments
 (0)