diff --git a/crates/oxc_language_server/src/linter.rs b/crates/oxc_language_server/src/linter.rs index dc4c8f479..20d4c3c0f 100644 --- a/crates/oxc_language_server/src/linter.rs +++ b/crates/oxc_language_server/src/linter.rs @@ -3,10 +3,9 @@ use std::{ fs, path::{Path, PathBuf}, rc::Rc, - sync::{Arc, RwLock}, + sync::Arc, }; -use crate::options::LintOptions; use miette::NamedSource; use oxc_allocator::Allocator; use oxc_diagnostics::{miette, Error, Severity}; @@ -17,7 +16,6 @@ use oxc_linter::{ }, LintContext, LintSettings, Linter, }; -use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::{SourceType, VALID_EXTENSIONS}; @@ -155,20 +153,14 @@ pub struct FixedContent { pub range: Range, } -type Plugin = Arc>>; - #[derive(Debug)] pub struct IsolatedLintHandler { - #[allow(unused)] - options: Arc, linter: Arc, - #[allow(unused)] - plugin: Plugin, } impl IsolatedLintHandler { - pub fn new(options: Arc, linter: Arc, plugin: Plugin) -> Self { - Self { options, linter, plugin } + pub fn new(linter: Arc) -> Self { + Self { linter } } pub fn run_single( @@ -176,51 +168,47 @@ impl IsolatedLintHandler { path: &Path, content: Option, ) -> Option> { - debug!("run single {path:?}"); if Self::is_wanted_ext(path) { - Some(Self::lint_path(&self.linter, path, &Arc::clone(&self.plugin), content).map_or( - vec![], - |(p, errors)| { - let mut diagnostics: Vec = - errors.into_iter().map(|e| e.into_diagnostic_report(&p)).collect(); - // a diagnostics connected from related_info to original diagnostic - let mut inverted_diagnostics = vec![]; - for d in &diagnostics { - let Some(ref related_info) = d.diagnostic.related_information else { - continue; - }; + Some(Self::lint_path(&self.linter, path, content).map_or(vec![], |(p, errors)| { + let mut diagnostics: Vec = + errors.into_iter().map(|e| e.into_diagnostic_report(&p)).collect(); + // a diagnostics connected from related_info to original diagnostic + let mut inverted_diagnostics = vec![]; + for d in &diagnostics { + let Some(ref related_info) = d.diagnostic.related_information else { + continue; + }; - let related_information = Some(vec![DiagnosticRelatedInformation { - location: lsp_types::Location { - uri: lsp_types::Url::from_file_path(path).unwrap(), - range: d.diagnostic.range, - }, - message: "original diagnostic".to_string(), - }]); - for r in related_info { - if r.location.range == d.diagnostic.range { - continue; - } - inverted_diagnostics.push(DiagnosticReport { - diagnostic: lsp_types::Diagnostic { - range: r.location.range, - severity: Some(DiagnosticSeverity::HINT), - code: None, - message: r.message.clone(), - source: Some("oxc".into()), - code_description: None, - related_information: related_information.clone(), - tags: None, - data: None, - }, - fixed_content: None, - }); + let related_information = Some(vec![DiagnosticRelatedInformation { + location: lsp_types::Location { + uri: lsp_types::Url::from_file_path(path).unwrap(), + range: d.diagnostic.range, + }, + message: "original diagnostic".to_string(), + }]); + for r in related_info { + if r.location.range == d.diagnostic.range { + continue; } + inverted_diagnostics.push(DiagnosticReport { + diagnostic: lsp_types::Diagnostic { + range: r.location.range, + severity: Some(DiagnosticSeverity::HINT), + code: None, + message: r.message.clone(), + source: Some("oxc".into()), + code_description: None, + related_information: related_information.clone(), + tags: None, + data: None, + }, + fixed_content: None, + }); } - diagnostics.append(&mut inverted_diagnostics); - diagnostics - }, - )) + } + diagnostics.append(&mut inverted_diagnostics); + diagnostics + })) } else { None } @@ -267,7 +255,6 @@ impl IsolatedLintHandler { fn lint_path( linter: &Linter, path: &Path, - plugin: &Plugin, source_text: Option, ) -> Option<(PathBuf, Vec)> { let ext = path.extension().and_then(std::ffi::OsStr::to_str)?; @@ -312,20 +299,11 @@ impl IsolatedLintHandler { return Some(Self::wrap_diagnostics(path, &original_source_text, reports, start)); }; - let mut lint_ctx = LintContext::new( + let lint_ctx = LintContext::new( path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic), LintSettings::default(), ); - { - if let Ok(guard) = plugin.read() { - if let Some(plugin) = &*guard { - plugin - .lint_file(&mut lint_ctx, make_relative_path_parts(&path.into())) - .unwrap(); - } - } - } let result = linter.run(lint_ctx); @@ -401,45 +379,21 @@ fn offset_to_position(offset: usize, source_text: &str) -> Option { #[derive(Debug)] pub struct ServerLinter { linter: Arc, - plugin: Plugin, } impl ServerLinter { pub fn new() -> Self { let linter = Linter::new().with_fix(true); - Self { linter: Arc::new(linter), plugin: Arc::new(RwLock::new(None)) } + Self { linter: Arc::new(linter) } } - pub fn make_plugin(&self, root_uri: &Url) { - let mut path = root_uri.to_file_path().unwrap(); - path.push(".oxc/"); - path.push("plugins"); - if path.exists() { - let mut plugin = self.plugin.write().unwrap(); - plugin.replace(LinterPlugin::new(&path).unwrap()); - } + pub fn new_with_linter(linter: Linter) -> Self { + Self { linter: Arc::new(linter) } } - pub fn run_single( - &self, - root_uri: &Url, - uri: &Url, - content: Option, - ) -> Option> { - let options = LintOptions { - paths: vec![root_uri.to_file_path().unwrap()], - ignore_path: "node_modules".into(), - ignore_pattern: vec!["!**/node_modules/**/*".into()], - fix: true, - ..LintOptions::default() - }; - - IsolatedLintHandler::new( - Arc::new(options), - Arc::clone(&self.linter), - Arc::clone(&self.plugin), - ) - .run_single(&uri.to_file_path().unwrap(), content) + pub fn run_single(&self, uri: &Url, content: Option) -> Option> { + IsolatedLintHandler::new(Arc::clone(&self.linter)) + .run_single(&uri.to_file_path().unwrap(), content) } } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 08d90d756..ef7370740 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -5,6 +5,7 @@ use crate::linter::{DiagnosticReport, ServerLinter}; use globset::Glob; use ignore::gitignore::Gitignore; use log::{debug, error, info}; +use oxc_linter::{LintOptions, Linter}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Debug; @@ -13,7 +14,7 @@ use std::str::FromStr; use dashmap::DashMap; use futures::future::join_all; -use tokio::sync::{Mutex, OnceCell, SetError}; +use tokio::sync::{Mutex, OnceCell, RwLock, SetError}; use tower_lsp::jsonrpc::{Error, ErrorCode, Result}; use tower_lsp::lsp_types::{ CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams, @@ -30,7 +31,7 @@ use tower_lsp::{Client, LanguageServer, LspService, Server}; struct Backend { client: Client, root_uri: OnceCell>, - server_linter: ServerLinter, + server_linter: RwLock, diagnostics_report_map: DashMap>, options: Mutex, gitignore_glob: Mutex>, @@ -79,6 +80,7 @@ impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { self.init(params.root_uri)?; self.init_ignore_glob().await; + self.init_linter_config().await; let options = params.initialization_options.and_then(|mut value| { let settings = value.get_mut("settings")?.take(); serde_json::from_value::(settings).ok() @@ -164,10 +166,6 @@ impl LanguageServer for Backend { async fn initialized(&self, _params: InitializedParams) { debug!("oxc initialized."); - - if let Some(Some(root_uri)) = self.root_uri.get() { - self.server_linter.make_plugin(root_uri); - } } async fn shutdown(&self) -> Result<()> { @@ -323,10 +321,36 @@ impl Backend { .await; } + async fn init_linter_config(&self) { + let Some(Some(uri)) = self.root_uri.get() else { + return; + }; + let Ok(root_path) = uri.to_file_path() else { + return; + }; + let mut config_path = None; + let rc_config = root_path.join(".eslintrc"); + if rc_config.exists() { + config_path = Some(rc_config); + } + let rc_json_config = root_path.join(".eslintrc.json"); + if rc_json_config.exists() { + config_path = Some(rc_json_config); + } + if let Some(config_path) = config_path { + let mut linter = self.server_linter.write().await; + *linter = ServerLinter::new_with_linter( + Linter::from_options( + LintOptions::default().with_fix(true).with_config_path(Some(config_path)), + ) + .expect("should initialized linter with new options"), + ); + } + } + async fn handle_file_update(&self, uri: Url, content: Option, version: Option) { - if let Some(Some(root_uri)) = self.root_uri.get() { - self.server_linter.make_plugin(root_uri); - if let Some(diagnostics) = self.server_linter.run_single(root_uri, &uri, content) { + if let Some(Some(_root_uri)) = self.root_uri.get() { + if let Some(diagnostics) = self.server_linter.read().await.run_single(&uri, content) { self.client .publish_diagnostics( uri.clone(), @@ -374,7 +398,7 @@ async fn main() { let (service, socket) = LspService::build(|client| Backend { client, root_uri: OnceCell::new(), - server_linter, + server_linter: RwLock::new(server_linter), diagnostics_report_map, options: Mutex::new(Options::default()), gitignore_glob: Mutex::new(None),