Skip to content

Commit f4c7ef3

Browse files
committed
refactor(language_server): introduce dummy ServerFormatter
1 parent fc596f4 commit f4c7ef3

File tree

9 files changed

+104
-7
lines changed

9 files changed

+104
-7
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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::ffi::OsStr;
2+
3+
use oxc_allocator::Allocator;
4+
use oxc_formatter::{FormatOptions, Formatter, get_supported_source_type};
5+
use oxc_parser::{ParseOptions, Parser};
6+
use oxc_span::SourceType;
7+
use tower_lsp_server::{
8+
UriExt,
9+
lsp_types::{Position, Range, TextEdit, Uri},
10+
};
11+
12+
use crate::LSP_MAX_INT;
13+
14+
pub struct ServerFormatter;
15+
16+
impl ServerFormatter {
17+
pub fn new() -> Self {
18+
Self {}
19+
}
20+
21+
#[expect(clippy::unused_self)]
22+
pub fn run_single(&self, uri: &Uri) -> Option<Vec<TextEdit>> {
23+
let path = uri.to_file_path()?;
24+
let source_type = get_supported_source_type(&path)?;
25+
let source_text = std::fs::read_to_string(path).expect("Failed to read file");
26+
27+
let allocator = Allocator::new();
28+
let ret = Parser::new(&allocator, &source_text, source_type)
29+
.with_options(ParseOptions {
30+
parse_regular_expression: false,
31+
// Enable all syntax features
32+
allow_v8_intrinsics: true,
33+
allow_return_outside_function: true,
34+
// `oxc_formatter` expects this to be false
35+
preserve_parens: false,
36+
})
37+
.parse();
38+
39+
if !ret.errors.is_empty() {
40+
return None;
41+
}
42+
43+
let options = FormatOptions {
44+
// semicolons: "always".parse().unwrap(),
45+
semicolons: "as-needed".parse().unwrap(),
46+
..FormatOptions::default()
47+
};
48+
let code = Formatter::new(&allocator, options).build(&ret.program);
49+
50+
Some(vec![TextEdit::new(
51+
Range::new(Position::new(0, 0), Position::new(LSP_MAX_INT, 0)),
52+
code,
53+
)])
54+
}
55+
}

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/linter/server_linter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ mod test {
492492
fn test_lint_on_run_on_type_on_save_without_type_aware() {
493493
Tester::new(
494494
"fixtures/linter/lint_on_run/on_type",
495-
Some(Options { type_aware: false, run: Run::OnType, ..Default::default() }),
495+
Some(LintOptions { type_aware: false, run: Run::OnType, ..Default::default() }),
496496
)
497497
.test_and_snapshot_single_file_with_run_type("on-save-no-type-aware.ts", Run::OnSave);
498498
}

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)