Skip to content

Commit

Permalink
feat: oxc ls support read configuration (#1940)
Browse files Browse the repository at this point in the history
1. Closed #1815
2. Very basically support configuration, could improve in the future
since our config is still under experimental.


![image](https://github.com/oxc-project/oxc/assets/17974631/6834c32f-586e-4fca-adc1-dfbeaadfaa3c)**config**
```json
{
  "rules": {
    "no-console": [
      "error",
      {
        "allow": [
          "info"
        ]
      }
    ]
  }
}
```
  • Loading branch information
IWANABETHATGUY authored Jan 8, 2024
1 parent b50c5ec commit 84dc690
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 103 deletions.
140 changes: 47 additions & 93 deletions crates/oxc_language_server/src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -155,72 +153,62 @@ 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(
&self,
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 {
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;
};

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,
});
}
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
}
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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)
}
}

Expand Down
44 changes: 34 additions & 10 deletions crates/oxc_language_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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>>,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit 84dc690

Please sign in to comment.