diff --git a/Cargo.lock b/Cargo.lock index 3387da7193d03..ec518dbb3b597 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1868,6 +1868,7 @@ dependencies = [ "oxc_allocator", "oxc_diagnostics", "oxc_linter", + "oxc_linter_plugin", "oxc_parser", "oxc_semantic", "oxc_span", diff --git a/crates/oxc_linter_plugin/src/lib.rs b/crates/oxc_linter_plugin/src/lib.rs index 1129a9386a73f..a2b7aa7a735e0 100644 --- a/crates/oxc_linter_plugin/src/lib.rs +++ b/crates/oxc_linter_plugin/src/lib.rs @@ -1,10 +1,10 @@ -#[cfg(test)] mod errors; -#[cfg(test)] mod plugin; -#[cfg(test)] mod raw_diagnostic; #[cfg(test)] mod spans; #[cfg(test)] mod test; +mod util; + +pub use {plugin::LinterPlugin, util::make_relative_path_parts}; diff --git a/crates/oxc_linter_plugin/src/plugin.rs b/crates/oxc_linter_plugin/src/plugin.rs index 43f688e86eee1..1fb0fc5da5d92 100644 --- a/crates/oxc_linter_plugin/src/plugin.rs +++ b/crates/oxc_linter_plugin/src/plugin.rs @@ -1,28 +1,16 @@ use std::{collections::BTreeMap, fmt::Debug, fs, path::PathBuf, rc::Rc, sync::Arc}; +use crate::{ + errors::{ErrorFromLinterPlugin, SpanStartOrEnd}, + raw_diagnostic::RawPluginDiagnostic, +}; use ignore::Walk; use miette::{NamedSource, SourceSpan}; -use oxc_allocator::Allocator; -use oxc_diagnostics::{ - miette::{self}, - Report, -}; +use oxc_diagnostics::miette::{self}; use oxc_linter::LintContext; -use oxc_parser::Parser; use oxc_query::{schema, Adapter}; -use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn}; -use oxc_span::SourceType; use serde::Deserialize; -use trustfall::{execute_query, FieldValue, Schema, TransparentValue}; - -use crate::{ - errors::{ - ErrorFromLinterPlugin, ExpectedTestToFailButPassed, ExpectedTestToPassButFailed, - SpanStartOrEnd, UnexpectedErrorsInFailTest, - }, - raw_diagnostic::RawPluginDiagnostic, - spans::{span_of_test_n, PassOrFail}, -}; +use trustfall::{execute_query, FieldValue, TransparentValue}; /// Represents a single parsed yaml plugin file. Includes /// the query, tests, and metadata about the query. @@ -56,18 +44,7 @@ pub struct SingleTest { /// Holds multiple parsed rules. #[derive(Debug)] pub struct LinterPlugin { - rules: Vec, - schema: &'static Schema, -} - -/// Whether to run all rules or only a specific rule. -#[allow(dead_code)] -pub enum RulesToRun { - /// Execute all rules. - All, - /// The rules to run will be the rules whose names are equal - /// to the string stored in the variant. - Only(String), + pub(crate) rules: Vec, } impl LinterPlugin { @@ -75,7 +52,7 @@ impl LinterPlugin { /// /// # Errors /// This function will error if it can't read a file, or if it can't parse a query - pub fn new(schema: &'static Schema, queries_path: &PathBuf) -> oxc_diagnostics::Result { + pub fn new(queries_path: &PathBuf) -> oxc_diagnostics::Result { let mut deserialized_queries = vec![]; for dir_entry_found_maybe in Walk::new(queries_path) { @@ -95,7 +72,7 @@ impl LinterPlugin { } } - Ok(Self { rules: deserialized_queries, schema }) + Ok(Self { rules: deserialized_queries }) } /// Run specific plugin rule by reference on parsed code. @@ -109,7 +86,6 @@ impl LinterPlugin { // the Adapter trait is implemented for a &Adapter, not just Adapter #[allow(clippy::redundant_allocation)] fn run_specific_plugin_rule( - &self, ctx: &mut LintContext, plugin: &InputQuery, adapter: &Arc<&Adapter<'_>>, @@ -125,7 +101,7 @@ impl LinterPlugin { let query_span = SourceSpan::new(0.into(), plugin.query.len().into()); let query_results = - execute_query(self.schema, Arc::clone(adapter), &plugin.query, plugin.args.clone()) + execute_query(schema(), Arc::clone(adapter), &plugin.query, plugin.args.clone()) .map_err(|err| ErrorFromLinterPlugin::Trustfall { error_message: err.to_string(), query_source: Arc::clone(&query_source), @@ -195,7 +171,7 @@ impl LinterPlugin { Ok(()) } - /// Run specific plugin rule by name or multiple plugin rules on parsed code. + /// Run all plugin rules on parsed code. /// /// # Errors /// Any errors that occur while linting the file, such as if the file can't be read, @@ -205,172 +181,33 @@ impl LinterPlugin { &self, ctx: &mut LintContext, relative_file_path_parts: Vec>, - rules_to_run: RulesToRun, ) -> oxc_diagnostics::Result<()> { let inner = Adapter::new(Rc::clone(ctx.semantic()), relative_file_path_parts); let adapter = Arc::from(&inner); - if let RulesToRun::Only(this_rule) = rules_to_run { - for rule in self.rules.iter().filter(|x| x.name == this_rule) { - self.run_specific_plugin_rule(ctx, rule, &adapter)?; - } - } else { - for rule in &self.rules { - self.run_specific_plugin_rule(ctx, rule, &adapter)?; - } + for rule in &self.rules { + Self::run_specific_plugin_rule(ctx, rule, &adapter)?; } Ok(()) } -} - -/// Run one individual test on unparsed code. -fn run_individual_test( - test: &SingleTest, - rule_name: &str, - plugin: &LinterPlugin, -) -> std::result::Result, Vec> { - let file_path = &test.relative_path.last().expect("there to be atleast 1 path part"); - let source_text = &test.code; - - let allocator = Allocator::default(); - let source_type = SourceType::from_path(file_path).unwrap(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - - // Handle parser errors - if !ret.errors.is_empty() { - return Err(ret.errors); - } - - let program = allocator.alloc(ret.program); - let SemanticBuilderReturn { semantic, errors } = - SemanticBuilder::new(source_text, source_type).with_trivias(ret.trivias).build(program); - - // Handle semantic errors - if !errors.is_empty() { - return Err(errors); - } - - let semantic = Rc::new(semantic); - - let mut lint_ctx = LintContext::new(&Rc::clone(&semantic)); - - let result = plugin.lint_file( - &mut lint_ctx, - test.relative_path.iter().map(|el| Some(el.clone())).collect::>(), - RulesToRun::Only(rule_name.to_string()), - ); - - // Handle query errors - if let Some(err) = result.err() { - return Err(vec![err]); - } - - // Return plugin made errors - Ok(lint_ctx.into_message().into_iter().map(|m| m.error).collect::>()) -} - -/// Enumerates and tests all queries at the path given. -/// # Errors -/// Unable to read any of the yaml rule files or unable to parse any of the yaml rule files, -/// or if any test expected to pass but failed, or if any test expected to fail but passed, -/// or query execution errors such as if the `span_start` and `span_end` are not both -/// understood types by the error reporting system. -pub fn test_queries(queries_to_test: &PathBuf) -> oxc_diagnostics::Result<()> { - let plugin = LinterPlugin::new(schema(), queries_to_test)?; - - for rule in &plugin.rules { - for (ix, test) in rule.tests.pass.iter().enumerate() { - let diagnostics_collected = run_individual_test(test, &rule.name, &plugin); - let source = Arc::new(NamedSource::new( - format!("./{}", test.relative_path.join("/")), - test.code.clone(), - )); - - match diagnostics_collected { - Err(errs) | Ok(errs) if !errs.is_empty() => { - let yaml_text = - fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; - let errors_with_code = errs - .into_iter() - .map(|e| { - // Don't change the sourcecode of errors that already have their own sourcecode - if e.source_code().is_some() { - e - } else { - // Add js code to errors that don't have code yet - e.with_source_code(Arc::clone(&source)) - } - }) - .collect(); - - return Err(ExpectedTestToPassButFailed { - errors: errors_with_code, - err_span: span_of_test_n(&yaml_text, ix, &test.code, &PassOrFail::Pass), - query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), - } - .into()); - } - _ => { /* Ignore the empty diagnostics, as it means the test passed. */ } - }; - } - - for (i, test) in rule.tests.fail.iter().enumerate() { - let diagnostics_collected = run_individual_test(test, &rule.name, &plugin); - let source = Arc::new(NamedSource::new( - format!("./{}", test.relative_path.join("/")), - test.code.clone(), - )); - - match diagnostics_collected { - Ok(errs) - if errs.len() == 1 // TODO: Handle more than one error - && matches!( - errs[0].downcast_ref::(), - Some(ErrorFromLinterPlugin::PluginGenerated(..)) - ) => - { /* Success case. */ } - Ok(errs) if errs.is_empty() => { - let yaml_text = - fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; - - return Err(ExpectedTestToFailButPassed { - err_span: span_of_test_n(&yaml_text, i, &test.code, &PassOrFail::Fail), - query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), - } - .into()); - } - Err(errs) | Ok(errs) => { - let yaml_text = - fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; - - return Err(UnexpectedErrorsInFailTest { - errors: errs - .into_iter() - .map(|e| { - // Don't change the sourcecode of errors that already have their own sourcecode - if e.source_code().is_some() { - e - } else { - e.with_source_code(Arc::clone(&source)) - } - }) - .collect(), - err_span: span_of_test_n(&yaml_text, i, &test.code, &PassOrFail::Fail), - query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), - } - .into()); - } - } - } - - if rule.tests.pass.len() + rule.tests.fail.len() > 0 { - println!( - "{} passed {} tests successfully.\n", - rule.name, - rule.tests.pass.len() + rule.tests.fail.len() - ); + /// Run specific plugin rule by name rules on parsed code. + /// + /// # Errors + /// Any errors that occur while linting the file, such as if the file can't be read, + /// or if the file can't be parsed, or if the query can't be executed, or if the query's + /// output types are wrong. + #[cfg(test)] + pub(crate) fn lint_file_with_rule( + &self, + ctx: &mut LintContext, + relative_file_path_parts: Vec>, + rule_name: &str, + ) -> oxc_diagnostics::Result<()> { + let inner = Adapter::new(Rc::clone(ctx.semantic()), relative_file_path_parts); + let adapter = Arc::from(&inner); + for rule in self.rules.iter().filter(|x| x.name == rule_name) { + Self::run_specific_plugin_rule(ctx, rule, &adapter)?; } + Ok(()) } - - Ok(()) } diff --git a/crates/oxc_linter_plugin/src/spans.rs b/crates/oxc_linter_plugin/src/spans.rs index 312f685677455..f1ce603bc056c 100644 --- a/crates/oxc_linter_plugin/src/spans.rs +++ b/crates/oxc_linter_plugin/src/spans.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use located_yaml::{YamlElt, YamlLoader}; -use miette::SourceSpan; +use oxc_diagnostics::miette::SourceSpan; /// Whether a rule is under the pass or the fail column of the plugin file. pub enum PassOrFail { diff --git a/crates/oxc_linter_plugin/src/test.rs b/crates/oxc_linter_plugin/src/test.rs index 1468f36277699..b498791574bb1 100644 --- a/crates/oxc_linter_plugin/src/test.rs +++ b/crates/oxc_linter_plugin/src/test.rs @@ -1,6 +1,183 @@ use std::path::Path; -use crate::plugin::test_queries; +use crate::{ + errors::{ + ExpectedTestToFailButPassed, ExpectedTestToPassButFailed, UnexpectedErrorsInFailTest, + }, + spans::{span_of_test_n, PassOrFail}, +}; +use crate::{plugin::SingleTest, LinterPlugin}; +use oxc_allocator::Allocator; +use oxc_diagnostics::Report; +use oxc_parser::Parser; +use oxc_semantic::{SemanticBuilder, SemanticBuilderReturn}; +use oxc_span::SourceType; +use std::path::PathBuf; + +/// Run one individual test on unparsed code. +#[cfg(test)] +fn run_individual_test( + test: &SingleTest, + rule_name: &str, + plugin: &LinterPlugin, +) -> std::result::Result, Vec> { + use std::rc::Rc; + + use oxc_linter::LintContext; + + let file_path = &test.relative_path.last().expect("there to be atleast 1 path part"); + let source_text = &test.code; + + let allocator = Allocator::default(); + let source_type = SourceType::from_path(file_path).unwrap(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + + // Handle parser errors + if !ret.errors.is_empty() { + return Err(ret.errors); + } + + let program = allocator.alloc(ret.program); + let SemanticBuilderReturn { semantic, errors } = + SemanticBuilder::new(source_text, source_type).with_trivias(ret.trivias).build(program); + + // Handle semantic errors + if !errors.is_empty() { + return Err(errors); + } + + let semantic = Rc::new(semantic); + + let mut lint_ctx = LintContext::new(&Rc::clone(&semantic)); + + let result = plugin.lint_file_with_rule( + &mut lint_ctx, + test.relative_path.iter().map(|el| Some(el.clone())).collect::>(), + rule_name, + ); + + // Handle query errors + if let Some(err) = result.err() { + return Err(vec![err]); + } + + // Return plugin made errors + Ok(lint_ctx.into_message().into_iter().map(|m| m.error).collect::>()) +} + +/// Enumerates and tests all queries at the path given. +/// # Errors +/// Unable to read any of the yaml rule files or unable to parse any of the yaml rule files, +/// or if any test expected to pass but failed, or if any test expected to fail but passed, +/// or query execution errors such as if the `span_start` and `span_end` are not both +/// understood types by the error reporting system. +#[cfg(test)] +pub fn test_queries(queries_to_test: &PathBuf) -> oxc_diagnostics::Result<()> { + use std::{fs, sync::Arc}; + + use miette::NamedSource; + + use crate::errors::ErrorFromLinterPlugin; + + let plugin = LinterPlugin::new(queries_to_test)?; + + for rule in &plugin.rules { + for (ix, test) in rule.tests.pass.iter().enumerate() { + let diagnostics_collected = run_individual_test(test, &rule.name, &plugin); + let source = Arc::new(NamedSource::new( + format!("./{}", test.relative_path.join("/")), + test.code.clone(), + )); + + match diagnostics_collected { + Err(errs) | Ok(errs) if !errs.is_empty() => { + let yaml_text = + fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; + + let errors_with_code = errs + .into_iter() + .map(|e| { + // Don't change the sourcecode of errors that already have their own sourcecode + if e.source_code().is_some() { + e + } else { + // Add js code to errors that don't have code yet + e.with_source_code(Arc::clone(&source)) + } + }) + .collect(); + + return Err(ExpectedTestToPassButFailed { + errors: errors_with_code, + err_span: span_of_test_n(&yaml_text, ix, &test.code, &PassOrFail::Pass), + query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), + } + .into()); + } + _ => { /* Ignore the empty diagnostics, as it means the test passed. */ } + }; + } + + for (i, test) in rule.tests.fail.iter().enumerate() { + let diagnostics_collected = run_individual_test(test, &rule.name, &plugin); + let source = Arc::new(NamedSource::new( + format!("./{}", test.relative_path.join("/")), + test.code.clone(), + )); + + match diagnostics_collected { + Ok(errs) + if errs.len() == 1 // TODO: Handle more than one error + && matches!( + errs[0].downcast_ref::(), + Some(ErrorFromLinterPlugin::PluginGenerated(..)) + ) => + { /* Success case. */ } + Ok(errs) if errs.is_empty() => { + let yaml_text = + fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; + + return Err(ExpectedTestToFailButPassed { + err_span: span_of_test_n(&yaml_text, i, &test.code, &PassOrFail::Fail), + query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), + } + .into()); + } + Err(errs) | Ok(errs) => { + let yaml_text = + fs::read_to_string(&rule.path).map_err(ErrorFromLinterPlugin::ReadFile)?; + + return Err(UnexpectedErrorsInFailTest { + errors: errs + .into_iter() + .map(|e| { + // Don't change the sourcecode of errors that already have their own sourcecode + if e.source_code().is_some() { + e + } else { + e.with_source_code(Arc::clone(&source)) + } + }) + .collect(), + err_span: span_of_test_n(&yaml_text, i, &test.code, &PassOrFail::Fail), + query: NamedSource::new(rule.path.to_string_lossy(), yaml_text), + } + .into()); + } + } + } + + if rule.tests.pass.len() + rule.tests.fail.len() > 0 { + println!( + "{} passed {} tests successfully.\n", + rule.name, + rule.tests.pass.len() + rule.tests.fail.len() + ); + } + } + + Ok(()) +} #[test] fn query_tests() -> oxc_diagnostics::Result<()> { diff --git a/crates/oxc_linter_plugin/src/util.rs b/crates/oxc_linter_plugin/src/util.rs new file mode 100644 index 0000000000000..24d521c71b80f --- /dev/null +++ b/crates/oxc_linter_plugin/src/util.rs @@ -0,0 +1,26 @@ +use std::path::{Component, Path, PathBuf}; + +use path_calculate::Calculate; + +/// Makes relative path part vec with a path relative to the current directory. +/// +/// ie: if current directory = /foo/ +/// and path = /foo/bar/baz.txt +/// then relative path parts = [Some("bar"), Some("baz.txt")] +/// +/// # Panics +/// Panics if the path component has any non-normal component after +/// a single normal path component. +pub fn make_relative_path_parts(path: &PathBuf) -> Vec> { + path.related_to(Path::new(".")) + .expect("to be able to get the relative path parts") + .components() + .skip_while(|x| !matches!(x, Component::Normal(_))) + .map(|path_component| { + let Component::Normal(component) = path_component else { + unreachable!("there should only be normal path components here") + }; + component.to_str().map(ToOwned::to_owned) + }) + .collect::>() +} diff --git a/editor/vscode/server/Cargo.toml b/editor/vscode/server/Cargo.toml index 65a2386cda6d9..88af084f8f0f8 100644 --- a/editor/vscode/server/Cargo.toml +++ b/editor/vscode/server/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "oxc_vscode" -version = "0.0.1" +name = "oxc_vscode" +version = "0.0.1" publish = false authors.workspace = true description.workspace = true @@ -15,18 +15,19 @@ categories.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -oxc_allocator = { workspace = true } -oxc_diagnostics = { workspace = true } -oxc_linter = { workspace = true } -oxc_parser = { workspace = true} -oxc_semantic = { workspace = true } -oxc_span = { workspace = true } -dashmap = { workspace = true } -env_logger = { workspace = true } -futures = { workspace = true } -ignore = { workspace = true, features = ["simd-accel"] } -miette = { workspace = true, features = ["fancy-no-backtrace"] } -rayon = { workspace = true } -ropey = { workspace = true } -tokio = { workspace = true, features = ["full"] } -tower-lsp = { workspace = true, features = ["proposed"]} +oxc_allocator = { workspace = true } +oxc_diagnostics = { workspace = true } +oxc_linter = { workspace = true } +oxc_parser = { workspace = true } +oxc_semantic = { workspace = true } +oxc_span = { workspace = true } +oxc_linter_plugin = { workspace = true } +dashmap = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +ignore = { workspace = true, features = ["simd-accel"] } +miette = { workspace = true, features = ["fancy-no-backtrace"] } +rayon = { workspace = true } +ropey = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tower-lsp = { workspace = true, features = ["proposed"] } diff --git a/editor/vscode/server/src/linter.rs b/editor/vscode/server/src/linter.rs index f52e8efc7eabd..f19c060d4465a 100644 --- a/editor/vscode/server/src/linter.rs +++ b/editor/vscode/server/src/linter.rs @@ -4,7 +4,7 @@ use std::{ rc::Rc, sync::{ atomic::{AtomicUsize, Ordering}, - mpsc, Arc, + mpsc, Arc, RwLock, }, }; @@ -14,6 +14,7 @@ use miette::NamedSource; use oxc_allocator::Allocator; use oxc_diagnostics::{miette, Error, Severity}; use oxc_linter::{LintContext, Linter}; +use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::{SourceType, VALID_EXTENSIONS}; @@ -129,15 +130,18 @@ pub struct FixedContent { pub range: Range, } +type Plugin = Arc>>; + #[derive(Debug)] pub struct IsolatedLintHandler { options: Arc, linter: Arc, + plugin: Plugin, } impl IsolatedLintHandler { - pub fn new(options: Arc, linter: Arc) -> Self { - Self { options, linter } + pub fn new(options: Arc, linter: Arc, plugin: Plugin) -> Self { + Self { options, linter, plugin } } /// # Panics @@ -153,9 +157,14 @@ impl IsolatedLintHandler { pub fn run_single(&self, path: &Path) -> Option> { if Self::is_wanted_ext(path) { - Some(Self::lint_path(&self.linter, path).map_or(vec![], |(p, errors)| { - errors.into_iter().map(|e| e.into_diagnostic_report(&p)).collect() - })) + Some( + Self::lint_path(&self.linter, path, Arc::clone(&self.plugin)).map_or( + vec![], + |(p, errors)| { + errors.into_iter().map(|e| e.into_diagnostic_report(&p)).collect() + }, + ), + ) } else { None } @@ -185,12 +194,14 @@ impl IsolatedLintHandler { }); let linter = Arc::clone(&self.linter); + let plugin = Arc::clone(&self.plugin); rayon::spawn(move || { while let Ok(path) = rx_path.recv() { let tx_error = tx_error.clone(); let linter = Arc::clone(&linter); + let plugin = Arc::clone(&plugin); rayon::spawn(move || { - if let Some(diagnostics) = Self::lint_path(&linter, &path) { + if let Some(diagnostics) = Self::lint_path(&linter, &path, plugin) { tx_error.send(diagnostics).unwrap(); } drop(tx_error); @@ -213,7 +224,11 @@ impl IsolatedLintHandler { .collect() } - fn lint_path(linter: &Linter, path: &Path) -> Option<(PathBuf, Vec)> { + fn lint_path( + linter: &Linter, + path: &Path, + plugin: Plugin, + ) -> Option<(PathBuf, Vec)> { let source_text = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read {path:?}")); let allocator = Allocator::default(); @@ -248,7 +263,16 @@ impl IsolatedLintHandler { return Some(Self::wrap_diagnostics(path, &source_text, reports)); }; - let lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); + let mut lint_ctx = LintContext::new(&Rc::new(semantic_ret.semantic)); + { + let plugin = plugin.read().unwrap(); + if let Some(plugin) = &*plugin { + plugin.lint_file(&mut lint_ctx, make_relative_path_parts(&path.into())).unwrap(); + } + } + + drop(plugin); // explicitly drop plugin so that we consume the plugin in this function's body + let result = linter.run(lint_ctx); if result.is_empty() { @@ -315,12 +339,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) } + Self { linter: Arc::new(linter), plugin: Arc::new(RwLock::new(None)) } + } + + pub fn make_plugin(&self, root_uri: &Url) { + let mut plugin = self.plugin.write().unwrap(); + let mut path = root_uri.to_file_path().unwrap(); + path.push(".oxc/"); + path.push("plugins"); + plugin.replace(LinterPlugin::new(&path).unwrap()); } pub fn run_full(&self, root_uri: &Url) -> Vec<(PathBuf, Vec)> { @@ -332,7 +365,12 @@ impl ServerLinter { ..LintOptions::default() }; - IsolatedLintHandler::new(Arc::new(options), Arc::clone(&self.linter)).run_full() + IsolatedLintHandler::new( + Arc::new(options), + Arc::clone(&self.linter), + Arc::clone(&self.plugin), + ) + .run_full() } pub fn run_single(&self, root_uri: &Url, uri: &Url) -> Option> { @@ -344,7 +382,11 @@ impl ServerLinter { ..LintOptions::default() }; - IsolatedLintHandler::new(Arc::new(options), Arc::clone(&self.linter)) - .run_single(&uri.to_file_path().unwrap()) + IsolatedLintHandler::new( + Arc::new(options), + Arc::clone(&self.linter), + Arc::clone(&self.plugin), + ) + .run_single(&uri.to_file_path().unwrap()) } } diff --git a/editor/vscode/server/src/main.rs b/editor/vscode/server/src/main.rs index b81b282b28e23..e8d943ca2fc7b 100644 --- a/editor/vscode/server/src/main.rs +++ b/editor/vscode/server/src/main.rs @@ -58,6 +58,7 @@ impl LanguageServer for Backend { self.client.log_message(MessageType::INFO, "oxc initialized.").await; if let Some(Some(root_uri)) = self.root_uri.get() { + self.server_linter.make_plugin(root_uri); let result = self.server_linter.run_full(root_uri); self.publish_all_diagnostics( @@ -154,6 +155,7 @@ impl Backend { async fn handle_file_update(&self, uri: Url) { 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) { self.client .publish_diagnostics(