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
18 changes: 12 additions & 6 deletions crates/oxc_language_server/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ use tower_lsp_server::{
};

use crate::{
ConcurrentHashMap, capabilities::Capabilities, file_system::LSPFileSystem,
options::WorkspaceOption, worker::WorkspaceWorker,
ConcurrentHashMap, ToolBuilder, capabilities::Capabilities, file_system::LSPFileSystem,
formatter::ServerFormatterBuilder, linter::ServerLinterBuilder, options::WorkspaceOption,
worker::WorkspaceWorker,
};

/// The Backend implements the LanguageServer trait to handle LSP requests and notifications.
Expand All @@ -40,6 +41,8 @@ use crate::{
pub struct Backend {
// The LSP client to communicate with the editor or IDE.
client: Client,
// The available tool builders to create tools like linters and formatters.
tool_builders: Vec<Box<dyn ToolBuilder>>,
// Each Workspace has it own worker with Linter (and in the future the formatter).
// We must respect each program inside with its own root folder
// and can not use shared programmes across multiple workspaces.
Expand Down Expand Up @@ -125,7 +128,7 @@ impl LanguageServer for Backend {
.map(|workspace_options| workspace_options.options.clone())
.unwrap_or_default();

worker.start_worker(option.clone()).await;
worker.start_worker(option.clone(), &self.tool_builders).await;
}
}

Expand Down Expand Up @@ -185,7 +188,7 @@ impl LanguageServer for Backend {
for (index, worker) in needed_configurations.values().enumerate() {
let configuration = configurations.get(index).unwrap_or(&serde_json::Value::Null);

worker.start_worker(configuration.clone()).await;
worker.start_worker(configuration.clone(), &self.tool_builders).await;
}
}

Expand Down Expand Up @@ -422,7 +425,7 @@ impl LanguageServer for Backend {
let worker = WorkspaceWorker::new(folder.uri.clone());
// get the configuration from the response and init the linter
let options = configurations.get(index).unwrap_or(&serde_json::Value::Null);
worker.start_worker(options.clone()).await;
worker.start_worker(options.clone(), &self.tool_builders).await;

added_registrations.extend(worker.init_watchers().await);
workers.push(worker);
Expand All @@ -432,7 +435,7 @@ impl LanguageServer for Backend {
for folder in params.event.added {
let worker = WorkspaceWorker::new(folder.uri);
// use default options
worker.start_worker(serde_json::Value::Null).await;
worker.start_worker(serde_json::Value::Null, &self.tool_builders).await;
workers.push(worker);
}
}
Expand Down Expand Up @@ -609,8 +612,11 @@ impl Backend {
/// It also holds the capabilities of the language server and an in-memory file system.
/// The client is used to communicate with the LSP client.
pub fn new(client: Client) -> Self {
let tools: Vec<Box<dyn ToolBuilder>> =
vec![Box::new(ServerFormatterBuilder), Box::new(ServerLinterBuilder)];
Self {
client,
tool_builders: tools,
workspace_workers: Arc::new(RwLock::new(vec![])),
capabilities: OnceCell::new(),
file_system: Arc::new(RwLock::new(LSPFileSystem::default())),
Expand Down
32 changes: 15 additions & 17 deletions crates/oxc_language_server/src/formatter/server_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ use crate::{
utils::normalize_path,
};

pub struct ServerFormatterBuilder {
root_uri: Uri,
options: LSPFormatOptions,
}
pub struct ServerFormatterBuilder;

impl ToolBuilder<ServerFormatter> for ServerFormatterBuilder {
fn new(root_uri: Uri, options: serde_json::Value) -> Self {
impl ServerFormatterBuilder {
pub fn build(root_uri: &Uri, options: serde_json::Value) -> ServerFormatter {
let options = match serde_json::from_value::<LSPFormatOptions>(options) {
Ok(opts) => opts,
Err(err) => {
Expand All @@ -35,22 +32,24 @@ impl ToolBuilder<ServerFormatter> for ServerFormatterBuilder {
LSPFormatOptions::default()
}
};
Self { root_uri, options }
}

fn build(&self) -> ServerFormatter {
if self.options.experimental {
if options.experimental {
debug!("experimental formatter enabled");
}
let root_path = self.root_uri.to_file_path().unwrap();
let root_path = root_uri.to_file_path().unwrap();

ServerFormatter::new(
Self::get_format_options(&root_path, self.options.config_path.as_ref()),
self.options.experimental,
Self::get_format_options(&root_path, options.config_path.as_ref()),
options.experimental,
)
}
}

impl ToolBuilder for ServerFormatterBuilder {
fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box<dyn Tool> {
Box::new(ServerFormatterBuilder::build(root_uri, options))
}
}

impl ServerFormatterBuilder {
fn get_format_options(root_path: &Path, config_path: Option<&String>) -> FormatOptions {
let oxfmtrc = if let Some(config) = Self::search_config_file(root_path, config_path) {
Expand Down Expand Up @@ -144,8 +143,7 @@ impl Tool for ServerFormatter {
};
}

let new_formatter =
ServerFormatterBuilder::new(root_uri.clone(), new_options_json.clone()).build();
let new_formatter = ServerFormatterBuilder::build(root_uri, new_options_json.clone());
let watch_patterns = new_formatter.get_watcher_patterns(new_options_json);
ToolRestartChanges {
tool: Some(Box::new(new_formatter)),
Expand Down Expand Up @@ -192,7 +190,7 @@ impl Tool for ServerFormatter {

// TODO: Check if the changed file is actually a config file

let new_formatter = ServerFormatterBuilder::new(root_uri.clone(), options).build();
let new_formatter = ServerFormatterBuilder::build(root_uri, options);

ToolRestartChanges {
tool: Some(Box::new(new_formatter)),
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_language_server/src/formatter/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use tower_lsp_server::{

use crate::{
formatter::server_formatter::{ServerFormatter, ServerFormatterBuilder},
tool::{Tool, ToolBuilder},
tool::Tool,
};

/// Given a file path relative to the crate root directory, return the absolute path of the file.
Expand Down Expand Up @@ -62,7 +62,7 @@ impl Tester<'_> {
.join(self.relative_root_dir);
let uri = Uri::from_file_path(absolute_path).expect("could not convert current dir to uri");

ServerFormatterBuilder::new(uri, self.options.clone()).build()
ServerFormatterBuilder::build(&uri, self.options.clone())
}

pub fn format_and_snapshot_single_file(&self, relative_file_path: &str) {
Expand Down
48 changes: 23 additions & 25 deletions crates/oxc_language_server/src/linter/server_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,12 @@ use crate::{
utils::normalize_path,
};

pub struct ServerLinterBuilder {
root_uri: Uri,
options: LSPLintOptions,
}
pub struct ServerLinterBuilder;

impl ToolBuilder<ServerLinter> for ServerLinterBuilder {
fn new(root_uri: Uri, options: serde_json::Value) -> Self {
impl ServerLinterBuilder {
/// # Panics
/// Panics if the root URI cannot be converted to a file path.
pub fn build(root_uri: &Uri, options: serde_json::Value) -> ServerLinter {
let options = match serde_json::from_value::<LSPLintOptions>(options) {
Ok(opts) => opts,
Err(e) => {
Expand All @@ -49,17 +48,11 @@ impl ToolBuilder<ServerLinter> for ServerLinterBuilder {
LSPLintOptions::default()
}
};
Self { root_uri, options }
}

/// # Panics
/// Panics if the root URI cannot be converted to a file path.
fn build(&self) -> ServerLinter {
let root_path = self.root_uri.to_file_path().unwrap();
let root_path = root_uri.to_file_path().unwrap();
let mut nested_ignore_patterns = Vec::new();
let (nested_configs, mut extended_paths) =
Self::create_nested_configs(&root_path, &self.options, &mut nested_ignore_patterns);
let config_path = self.options.config_path.as_ref().map_or(LINT_CONFIG_FILE, |v| v);
Self::create_nested_configs(&root_path, &options, &mut nested_ignore_patterns);
let config_path = options.config_path.as_ref().map_or(LINT_CONFIG_FILE, |v| v);
let config = normalize_path(root_path.join(config_path));
let oxlintrc = if config.try_exists().is_ok_and(|exists| exists) {
if let Ok(oxlintrc) = Oxlintrc::from_file(&config) {
Expand All @@ -84,8 +77,8 @@ impl ToolBuilder<ServerLinter> for ServerLinterBuilder {
.unwrap_or_default();

// TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
let use_nested_config = self.options.use_nested_configs();
let fix_kind = FixKind::from(self.options.fix_kind.clone());
let use_nested_config = options.use_nested_configs();
let fix_kind = FixKind::from(options.fix_kind.clone());

let use_cross_module = config_builder.plugins().has_import()
|| (use_nested_config
Expand All @@ -99,7 +92,7 @@ impl ToolBuilder<ServerLinter> for ServerLinterBuilder {

let lint_options = LintOptions {
fix: fix_kind,
report_unused_directive: match self.options.unused_disable_directives {
report_unused_directive: match options.unused_disable_directives {
UnusedDisableDirectives::Allow => None, // or AllowWarnDeny::Allow, should be the same?
UnusedDisableDirectives::Warn => Some(AllowWarnDeny::Warn),
UnusedDisableDirectives::Deny => Some(AllowWarnDeny::Deny),
Expand All @@ -125,18 +118,18 @@ impl ToolBuilder<ServerLinter> for ServerLinterBuilder {
config_store,
&IsolatedLintHandlerOptions {
use_cross_module,
type_aware: self.options.type_aware,
fix_kind: FixKind::from(self.options.fix_kind.clone()),
type_aware: options.type_aware,
fix_kind: FixKind::from(options.fix_kind.clone()),
root_path: root_path.to_path_buf(),
tsconfig_path: self.options.ts_config_path.as_ref().map(|path| {
tsconfig_path: options.ts_config_path.as_ref().map(|path| {
let path = Path::new(path).to_path_buf();
if path.is_relative() { root_path.join(path) } else { path }
}),
},
);

ServerLinter::new(
self.options.run,
options.run,
root_path.to_path_buf(),
isolated_linter,
LintIgnoreMatcher::new(&base_patterns, &root_path, nested_ignore_patterns),
Expand All @@ -146,6 +139,12 @@ impl ToolBuilder<ServerLinter> for ServerLinterBuilder {
}
}

impl ToolBuilder for ServerLinterBuilder {
fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box<dyn Tool> {
Box::new(ServerLinterBuilder::build(root_uri, options))
}
}

impl ServerLinterBuilder {
/// Searches inside root_uri recursively for the default oxlint config files
/// and insert them inside the nested configuration
Expand Down Expand Up @@ -291,8 +290,7 @@ impl Tool for ServerLinter {

// get the cached files before refreshing the linter, and revalidate them after
let cached_files = self.get_cached_files_of_diagnostics();
let new_linter =
ServerLinterBuilder::new(root_uri.clone(), new_options_json.clone()).build();
let new_linter = ServerLinterBuilder::build(root_uri, new_options_json.clone());
let diagnostics = Some(new_linter.revalidate_diagnostics(cached_files));

let patterns = {
Expand Down Expand Up @@ -346,7 +344,7 @@ impl Tool for ServerLinter {
options: serde_json::Value,
) -> ToolRestartChanges {
// TODO: Check if the changed file is actually a config file (including extended paths)
let new_linter = ServerLinterBuilder::new(root_uri.clone(), options).build();
let new_linter = ServerLinterBuilder::build(root_uri, options);

// get the cached files before refreshing the linter, and revalidate them after
let cached_files = self.get_cached_files_of_diagnostics();
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_language_server/src/linter/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use tower_lsp_server::{

use crate::{
linter::{ServerLinterBuilder, server_linter::ServerLinter},
tool::{Tool, ToolBuilder},
tool::Tool,
};

/// Given a file path relative to the crate root directory, return the absolute path of the file.
Expand Down Expand Up @@ -171,7 +171,7 @@ impl Tester<'_> {
.join(self.relative_root_dir);
let uri = Uri::from_file_path(absolute_path).expect("could not convert current dir to uri");

ServerLinterBuilder::new(uri, self.options.clone()).build()
ServerLinterBuilder::build(&uri, self.options.clone())
}

/// Given a relative file path (relative to `oxc_language_server` crate root), run the linter
Expand Down
5 changes: 2 additions & 3 deletions crates/oxc_language_server/src/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ use tower_lsp_server::{
},
};

pub trait ToolBuilder<T: Tool> {
fn new(root_uri: Uri, options: serde_json::Value) -> Self;
fn build(&self) -> T;
pub trait ToolBuilder: Send + Sync {
fn build_boxed(&self, root_uri: &Uri, options: serde_json::Value) -> Box<dyn Tool>;
}

pub trait Tool: Send + Sync {
Expand Down
24 changes: 12 additions & 12 deletions crates/oxc_language_server/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ use tower_lsp_server::{
},
};

use crate::{
formatter::ServerFormatterBuilder,
linter::ServerLinterBuilder,
tool::{Tool, ToolBuilder},
};
use crate::tool::{Tool, ToolBuilder};

/// A worker that manages the individual tools for a specific workspace
/// and reports back the results to the [`Backend`](crate::backend::Backend).
Expand Down Expand Up @@ -60,11 +56,9 @@ impl WorkspaceWorker {

/// Start all programs (linter, formatter) for the worker.
/// This should be called after the client has sent the workspace configuration.
pub async fn start_worker(&self, options: serde_json::Value) {
*self.tools.write().await = vec![
Box::new(ServerLinterBuilder::new(self.root_uri.clone(), options.clone()).build()),
Box::new(ServerFormatterBuilder::new(self.root_uri.clone(), options.clone()).build()),
];
pub async fn start_worker(&self, options: serde_json::Value, tools: &[Box<dyn ToolBuilder>]) {
*self.tools.write().await =
tools.iter().map(|tool| tool.build_boxed(&self.root_uri, options.clone())).collect();

*self.options.lock().await = Some(options);
}
Expand Down Expand Up @@ -418,7 +412,10 @@ mod test_watchers {
},
};

use crate::worker::WorkspaceWorker;
use crate::{
ToolBuilder, formatter::ServerFormatterBuilder, linter::ServerLinterBuilder,
worker::WorkspaceWorker,
};

struct Tester {
pub worker: WorkspaceWorker,
Expand All @@ -443,7 +440,10 @@ mod test_watchers {
options: serde_json::Value,
) -> WorkspaceWorker {
let worker = WorkspaceWorker::new(absolute_path);
worker.start_worker(options).await;
let tools: Vec<Box<dyn ToolBuilder>> =
vec![Box::new(ServerLinterBuilder), Box::new(ServerFormatterBuilder)];

worker.start_worker(options, &tools).await;

worker
}
Expand Down
Loading