Skip to content

Commit bad4448

Browse files
committed
feat(linter): introduce ExternalLinter struct
1 parent f7d998b commit bad4448

File tree

17 files changed

+139
-40
lines changed

17 files changed

+139
-40
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/oxlint/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ pub mod cli {
1010
pub use crate::{command::*, lint::LintRunner, result::CliRunResult, runner::Runner};
1111
}
1212

13+
pub use oxc_linter::{ExternalLinter, ExternalLinterCb, ExternalLinterLoadPluginCb};
14+
1315
#[cfg(all(feature = "allocator", not(miri), not(target_family = "wasm")))]
1416
#[global_allocator]
1517
static GLOBAL: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;
1618

1719
use cli::{CliRunResult, LintRunner, Runner};
1820
use std::{ffi::OsStr, io::BufWriter};
1921

20-
pub fn lint() -> CliRunResult {
22+
pub fn lint(external_linter: Option<ExternalLinter>) -> CliRunResult {
2123
init_tracing();
2224
init_miette();
2325

@@ -49,7 +51,7 @@ pub fn lint() -> CliRunResult {
4951
// See `https://github.com/rust-lang/rust/issues/60673`.
5052
let mut stdout = BufWriter::new(std::io::stdout());
5153

52-
LintRunner::new(command).run(&mut stdout)
54+
LintRunner::new(command, external_linter).run(&mut stdout)
5355
}
5456

5557
// Initialize the data which relies on `is_atty` system calls so they don't block subsequent threads.

apps/oxlint/src/lint.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
1313
use oxc_allocator::AllocatorPool;
1414
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
1515
use oxc_linter::{
16-
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter,
17-
LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
16+
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, ExternalLinter, InvalidFilterKind,
17+
LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
1818
};
1919
use rustc_hash::{FxHashMap, FxHashSet};
2020
use serde_json::Value;
@@ -29,13 +29,18 @@ use crate::{
2929
pub struct LintRunner {
3030
options: LintCommand,
3131
cwd: PathBuf,
32+
external_linter: Option<ExternalLinter>,
3233
}
3334

3435
impl Runner for LintRunner {
3536
type Options = LintCommand;
3637

37-
fn new(options: Self::Options) -> Self {
38-
Self { options, cwd: env::current_dir().expect("Failed to get current working directory") }
38+
fn new(options: Self::Options, external_linter: Option<ExternalLinter>) -> Self {
39+
Self {
40+
options,
41+
cwd: env::current_dir().expect("Failed to get current working directory"),
42+
external_linter,
43+
}
3944
}
4045

4146
fn run(self, stdout: &mut dyn Write) -> CliRunResult {
@@ -63,6 +68,8 @@ impl Runner for LintRunner {
6368
..
6469
} = self.options;
6570

71+
let external_linter = self.external_linter.as_ref();
72+
6673
let search_for_nested_configs = !disable_nested_config &&
6774
// If the `--config` option is explicitly passed, we should not search for nested config files
6875
// as the passed config file takes absolute precedence.
@@ -173,7 +180,7 @@ impl Runner for LintRunner {
173180
let handler = GraphicalReportHandler::new();
174181

175182
let nested_configs = if search_for_nested_configs {
176-
match Self::get_nested_configs(stdout, &handler, &filters, &paths) {
183+
match Self::get_nested_configs(stdout, &handler, &filters, &paths, external_linter) {
177184
Ok(v) => v,
178185
Err(v) => return v,
179186
}
@@ -192,20 +199,21 @@ impl Runner for LintRunner {
192199
} else {
193200
None
194201
};
195-
let config_builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
196-
Ok(builder) => builder,
197-
Err(e) => {
198-
print_and_flush_stdout(
199-
stdout,
200-
&format!(
201-
"Failed to parse configuration file.\n{}\n",
202-
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
203-
),
204-
);
205-
return CliRunResult::InvalidOptionConfig;
202+
let config_builder =
203+
match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter) {
204+
Ok(builder) => builder,
205+
Err(e) => {
206+
print_and_flush_stdout(
207+
stdout,
208+
&format!(
209+
"Failed to parse configuration file.\n{}\n",
210+
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
211+
),
212+
);
213+
return CliRunResult::InvalidOptionConfig;
214+
}
206215
}
207-
}
208-
.with_filters(&filters);
216+
.with_filters(&filters);
209217

210218
if let Some(basic_config_file) = oxlintrc_for_print {
211219
let config_file = config_builder.resolve_final_config_file(basic_config_file);
@@ -387,6 +395,7 @@ impl LintRunner {
387395
handler: &GraphicalReportHandler,
388396
filters: &Vec<LintFilter>,
389397
paths: &Vec<Arc<OsStr>>,
398+
external_linter: Option<&ExternalLinter>,
390399
) -> Result<FxHashMap<PathBuf, Config>, CliRunResult> {
391400
// TODO(perf): benchmark whether or not it is worth it to store the configurations on a
392401
// per-file or per-directory basis, to avoid calling `.parent()` on every path.
@@ -417,7 +426,8 @@ impl LintRunner {
417426
// iterate over each config and build the ConfigStore
418427
for (dir, oxlintrc) in nested_oxlintrc {
419428
// TODO(refactor): clean up all of the error handling in this function
420-
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
429+
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter)
430+
{
421431
Ok(builder) => builder,
422432
Err(e) => {
423433
print_and_flush_stdout(

apps/oxlint/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use oxlint::{cli::CliRunResult, lint};
22

33
fn main() -> CliRunResult {
4-
lint()
4+
lint(None)
55
}

apps/oxlint/src/runner.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use std::io::Write;
22

3+
use oxc_linter::ExternalLinter;
4+
35
use crate::cli::CliRunResult;
46

57
/// A trait for exposing functionality to the CLI.
68
pub trait Runner {
79
type Options;
810

9-
fn new(matches: Self::Options) -> Self;
11+
fn new(matches: Self::Options, external_linter: Option<ExternalLinter>) -> Self;
1012

1113
/// Executes the runner, providing some result to the CLI.
1214
fn run(self, stdout: &mut dyn Write) -> CliRunResult;

apps/oxlint/src/tester.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl Tester {
3737

3838
let options = lint_command().run_inner(new_args.as_slice()).unwrap();
3939
let mut output = Vec::new();
40-
let _ = LintRunner::new(options).with_cwd(self.cwd.clone()).run(&mut output);
40+
let _ = LintRunner::new(options, None).with_cwd(self.cwd.clone()).run(&mut output);
4141
}
4242

4343
pub fn test_and_snapshot(&self, args: &[&str]) {
@@ -59,7 +59,7 @@ impl Tester {
5959
format!("working directory: {}\n", relative_dir.to_str().unwrap()).as_bytes(),
6060
);
6161
output.extend_from_slice(b"----------\n");
62-
let result = LintRunner::new(options).with_cwd(self.cwd.clone()).run(&mut output);
62+
let result = LintRunner::new(options, None).with_cwd(self.cwd.clone()).run(&mut output);
6363

6464
output.extend_from_slice(b"----------\n");
6565
output.extend_from_slice(format!("CLI result: {result:?}\n").as_bytes());

crates/oxc_language_server/src/linter/server_linter.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl ServerLinter {
5454

5555
// clone because we are returning it for ignore builder
5656
let config_builder =
57-
ConfigStoreBuilder::from_oxlintrc(false, oxlintrc.clone()).unwrap_or_default();
57+
ConfigStoreBuilder::from_oxlintrc(false, oxlintrc.clone(), None).unwrap_or_default();
5858

5959
// TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
6060
let use_nested_config = options.use_nested_configs();
@@ -131,7 +131,7 @@ impl ServerLinter {
131131
warn!("Skipping invalid config file: {}", file_path.display());
132132
continue;
133133
};
134-
let Ok(config_store_builder) = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc)
134+
let Ok(config_store_builder) = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, None)
135135
else {
136136
warn!("Skipping config (builder failed): {}", file_path.display());
137137
continue;

crates/oxc_linter/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ language-tags = { workspace = true }
5757
lazy-regex = { workspace = true }
5858
lazy_static = { workspace = true }
5959
memchr = { workspace = true }
60+
napi = { workspace = true }
61+
napi-derive = { workspace = true }
6062
nonmax = { workspace = true }
6163
phf = { workspace = true, features = ["macros"] }
6264
rayon = { workspace = true }

crates/oxc_linter/src/config/config_builder.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use oxc_span::{CompactStr, format_compact_str};
1111
use crate::{
1212
AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum,
1313
config::{ESLintRule, LintPlugins, OxlintOverrides, OxlintRules, overrides::OxlintOverride},
14+
external_linter::ExternalLinter,
1415
rules::RULES,
1516
};
1617

@@ -83,6 +84,7 @@ impl ConfigStoreBuilder {
8384
pub fn from_oxlintrc(
8485
start_empty: bool,
8586
oxlintrc: Oxlintrc,
87+
_external_linter: Option<&ExternalLinter>,
8688
) -> Result<Self, ConfigBuilderError> {
8789
// TODO: this can be cached to avoid re-computing the same oxlintrc
8890
fn resolve_oxlintrc_config(
@@ -375,7 +377,7 @@ impl TryFrom<Oxlintrc> for ConfigStoreBuilder {
375377

376378
#[inline]
377379
fn try_from(oxlintrc: Oxlintrc) -> Result<Self, Self::Error> {
378-
Self::from_oxlintrc(false, oxlintrc)
380+
Self::from_oxlintrc(false, oxlintrc, None)
379381
}
380382
}
381383

@@ -640,7 +642,7 @@ mod test {
640642
"#,
641643
)
642644
.unwrap();
643-
let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).unwrap();
645+
let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, None).unwrap();
644646
for (rule, severity) in &builder.rules {
645647
let name = rule.name();
646648
let plugin = rule.plugin_name();
@@ -815,6 +817,7 @@ mod test {
815817
"fixtures/extends_config/extends_invalid_config.json",
816818
))
817819
.unwrap(),
820+
None,
818821
);
819822
let err = invalid_config.unwrap_err();
820823
assert!(matches!(err, ConfigBuilderError::InvalidConfigFile { .. }));
@@ -943,12 +946,18 @@ mod test {
943946
}
944947

945948
fn config_store_from_path(path: &str) -> Config {
946-
ConfigStoreBuilder::from_oxlintrc(true, Oxlintrc::from_file(&PathBuf::from(path)).unwrap())
947-
.unwrap()
948-
.build()
949+
ConfigStoreBuilder::from_oxlintrc(
950+
true,
951+
Oxlintrc::from_file(&PathBuf::from(path)).unwrap(),
952+
None,
953+
)
954+
.unwrap()
955+
.build()
949956
}
950957

951958
fn config_store_from_str(s: &str) -> Config {
952-
ConfigStoreBuilder::from_oxlintrc(true, serde_json::from_str(s).unwrap()).unwrap().build()
959+
ConfigStoreBuilder::from_oxlintrc(true, serde_json::from_str(s).unwrap(), None)
960+
.unwrap()
961+
.build()
953962
}
954963
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::{fmt::Debug, sync::Arc};
2+
3+
use napi::{Status, bindgen_prelude::Promise, threadsafe_function::ThreadsafeFunction};
4+
use napi_derive::napi;
5+
6+
#[napi]
7+
pub type ExternalLinterCb =
8+
Arc<ThreadsafeFunction<(), /* TODO: correct return type */ (), (), Status, false>>;
9+
10+
#[napi]
11+
pub type ExternalLinterLoadPluginCb =
12+
Arc<ThreadsafeFunction<String, Promise<PluginLoadResult>, String, Status, false>>;
13+
14+
#[napi]
15+
pub enum PluginLoadResult {
16+
Success,
17+
Failure(String),
18+
}
19+
20+
#[derive(Clone)]
21+
#[expect(dead_code)]
22+
pub struct ExternalLinter {
23+
pub(crate) load_plugin: ExternalLinterLoadPluginCb,
24+
pub(crate) run: ExternalLinterCb,
25+
}
26+
27+
impl ExternalLinter {
28+
pub fn new(run: ExternalLinterCb, load_plugin: ExternalLinterLoadPluginCb) -> Self {
29+
ExternalLinter { load_plugin, run }
30+
}
31+
}
32+
33+
impl Debug for ExternalLinter {
34+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35+
f.debug_struct("ExternalLinter").finish()
36+
}
37+
}

0 commit comments

Comments
 (0)