mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat: oxc ls support read configuration (#1940)
1. Closed #1815 2. Very basically support configuration, could improve in the future since our config is still under experimental. **config** ```json { "rules": { "no-console": [ "error", { "allow": [ "info" ] } ] } } ```
This commit is contained in:
parent
b50c5ec623
commit
84dc690c40
2 changed files with 81 additions and 103 deletions
|
|
@ -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<RwLock<Option<LinterPlugin>>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IsolatedLintHandler {
|
||||
#[allow(unused)]
|
||||
options: Arc<LintOptions>,
|
||||
linter: Arc<Linter>,
|
||||
#[allow(unused)]
|
||||
plugin: Plugin,
|
||||
}
|
||||
|
||||
impl IsolatedLintHandler {
|
||||
pub fn new(options: Arc<LintOptions>, linter: Arc<Linter>, plugin: Plugin) -> Self {
|
||||
Self { options, linter, plugin }
|
||||
pub fn new(linter: Arc<Linter>) -> Self {
|
||||
Self { linter }
|
||||
}
|
||||
|
||||
pub fn run_single(
|
||||
|
|
@ -176,51 +168,47 @@ impl IsolatedLintHandler {
|
|||
path: &Path,
|
||||
content: Option<String>,
|
||||
) -> Option<Vec<DiagnosticReport>> {
|
||||
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<DiagnosticReport> =
|
||||
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<DiagnosticReport> =
|
||||
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<String>,
|
||||
) -> Option<(PathBuf, Vec<ErrorWithPosition>)> {
|
||||
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<Position> {
|
|||
#[derive(Debug)]
|
||||
pub struct ServerLinter {
|
||||
linter: Arc<Linter>,
|
||||
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<String>,
|
||||
) -> Option<Vec<DiagnosticReport>> {
|
||||
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<String>) -> Option<Vec<DiagnosticReport>> {
|
||||
IsolatedLintHandler::new(Arc::clone(&self.linter))
|
||||
.run_single(&uri.to_file_path().unwrap(), content)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Option<Url>>,
|
||||
server_linter: ServerLinter,
|
||||
server_linter: RwLock<ServerLinter>,
|
||||
diagnostics_report_map: DashMap<String, Vec<DiagnosticReport>>,
|
||||
options: Mutex<Options>,
|
||||
gitignore_glob: Mutex<Option<Gitignore>>,
|
||||
|
|
@ -79,6 +80,7 @@ impl LanguageServer for Backend {
|
|||
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
|
||||
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::<Options>(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<String>, version: Option<i32>) {
|
||||
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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue