Skip to content

Commit 6694fb7

Browse files
committed
feat(linter): load custom JS plugins
1 parent 3f91f24 commit 6694fb7

File tree

18 files changed

+427
-161
lines changed

18 files changed

+427
-161
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ oxc_tasks_transform_checker = { path = "tasks/transform_checker" }
140140
oxlint = { path = "apps/oxlint" }
141141

142142
# Relaxed version so the user can decide which version to use.
143-
napi = "3.0.0-beta"
143+
napi = { version = "3.0.0-beta", features = ["tokio_rt"] }
144144
napi-build = "2.2.1"
145145
napi-derive = "3.0.0-beta"
146146

@@ -235,7 +235,13 @@ ureq = { version = "3.0.11", default-features = false }
235235
walkdir = "2.5.0"
236236

237237
[workspace.metadata.cargo-shear]
238-
ignored = ["napi", "oxc_transform_napi", "oxc_parser_napi", "prettyplease", "lazy_static"]
238+
ignored = [
239+
"napi",
240+
"oxc_transform_napi",
241+
"oxc_parser_napi",
242+
"prettyplease",
243+
"lazy_static",
244+
]
239245

240246
[profile.dev]
241247
# Disabling debug info speeds up local and CI builds,

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

@@ -45,7 +47,7 @@ pub fn lint() -> CliRunResult {
4547
// See `https://github.com/rust-lang/rust/issues/60673`.
4648
let mut stdout = BufWriter::new(std::io::stdout());
4749

48-
LintRunner::new(command).run(&mut stdout)
50+
LintRunner::new(command, external_linter).run(&mut stdout)
4951
}
5052

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

apps/oxlint/src/lint.rs

Lines changed: 31 additions & 20 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 {
@@ -59,10 +64,12 @@ impl Runner for LintRunner {
5964
enable_plugins,
6065
misc_options,
6166
disable_nested_config,
62-
inline_config_options,
67+
ref inline_config_options,
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,22 @@ 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, self.external_linter.as_ref())
204+
{
205+
Ok(builder) => builder,
206+
Err(e) => {
207+
print_and_flush_stdout(
208+
stdout,
209+
&format!(
210+
"Failed to parse configuration file.\n{}\n",
211+
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
212+
),
213+
);
214+
return CliRunResult::InvalidOptionConfig;
215+
}
206216
}
207-
}
208-
.with_filters(&filters);
217+
.with_filters(&filters);
209218

210219
if let Some(basic_config_file) = oxlintrc_for_print {
211220
let config_file = config_builder.resolve_final_config_file(basic_config_file);
@@ -387,6 +396,7 @@ impl LintRunner {
387396
handler: &GraphicalReportHandler,
388397
filters: &Vec<LintFilter>,
389398
paths: &Vec<Arc<OsStr>>,
399+
external_linter: Option<&ExternalLinter>,
390400
) -> Result<FxHashMap<PathBuf, Config>, CliRunResult> {
391401
// TODO(perf): benchmark whether or not it is worth it to store the configurations on a
392402
// per-file or per-directory basis, to avoid calling `.parent()` on every path.
@@ -417,7 +427,8 @@ impl LintRunner {
417427
// iterate over each config and build the ConfigStore
418428
for (dir, oxlintrc) in nested_oxlintrc {
419429
// TODO(refactor): clean up all of the error handling in this function
420-
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
430+
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter)
431+
{
421432
Ok(builder) => builder,
422433
Err(e) => {
423434
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ constcat = { workspace = true }
4848
convert_case = { workspace = true }
4949
cow-utils = { workspace = true }
5050
fast-glob = { workspace = true }
51+
futures = { workspace = true }
5152
globset = { workspace = true }
5253
indexmap = { workspace = true, features = ["rayon"] }
5354
itertools = { workspace = true }
@@ -57,6 +58,8 @@ language-tags = { workspace = true }
5758
lazy-regex = { workspace = true }
5859
lazy_static = { workspace = true }
5960
memchr = { workspace = true }
61+
napi = { workspace = true }
62+
napi-derive = { workspace = true }
6063
nonmax = { workspace = true }
6164
phf = { workspace = true, features = ["macros"] }
6265
rayon = { workspace = true }

0 commit comments

Comments
 (0)