Skip to content

Commit bec71cb

Browse files
committed
feat(linter): load custom JS plugins
1 parent b8ede17 commit bec71cb

File tree

24 files changed

+551
-225
lines changed

24 files changed

+551
-225
lines changed

Cargo.lock

Lines changed: 4 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/command/lint.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::PathBuf;
22

33
use bpaf::Bpaf;
4-
use oxc_linter::{AllowWarnDeny, FixKind, LintPlugins};
4+
use oxc_linter::{AllowWarnDeny, BuiltinLintPlugins, FixKind, LintPlugins};
55

66
use crate::output_formatter::OutputFormat;
77

@@ -328,23 +328,25 @@ impl OverrideToggle {
328328

329329
impl EnablePlugins {
330330
pub fn apply_overrides(&self, plugins: &mut LintPlugins) {
331-
self.react_plugin.inspect(|yes| plugins.set(LintPlugins::REACT, yes));
332-
self.unicorn_plugin.inspect(|yes| plugins.set(LintPlugins::UNICORN, yes));
333-
self.oxc_plugin.inspect(|yes| plugins.set(LintPlugins::OXC, yes));
334-
self.typescript_plugin.inspect(|yes| plugins.set(LintPlugins::TYPESCRIPT, yes));
335-
self.import_plugin.inspect(|yes| plugins.set(LintPlugins::IMPORT, yes));
336-
self.jsdoc_plugin.inspect(|yes| plugins.set(LintPlugins::JSDOC, yes));
337-
self.jest_plugin.inspect(|yes| plugins.set(LintPlugins::JEST, yes));
338-
self.vitest_plugin.inspect(|yes| plugins.set(LintPlugins::VITEST, yes));
339-
self.jsx_a11y_plugin.inspect(|yes| plugins.set(LintPlugins::JSX_A11Y, yes));
340-
self.nextjs_plugin.inspect(|yes| plugins.set(LintPlugins::NEXTJS, yes));
341-
self.react_perf_plugin.inspect(|yes| plugins.set(LintPlugins::REACT_PERF, yes));
342-
self.promise_plugin.inspect(|yes| plugins.set(LintPlugins::PROMISE, yes));
343-
self.node_plugin.inspect(|yes| plugins.set(LintPlugins::NODE, yes));
331+
self.react_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::REACT, yes));
332+
self.unicorn_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::UNICORN, yes));
333+
self.oxc_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::OXC, yes));
334+
self.typescript_plugin
335+
.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::TYPESCRIPT, yes));
336+
self.import_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::IMPORT, yes));
337+
self.jsdoc_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::JSDOC, yes));
338+
self.jest_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::JEST, yes));
339+
self.vitest_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::VITEST, yes));
340+
self.jsx_a11y_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::JSX_A11Y, yes));
341+
self.nextjs_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::NEXTJS, yes));
342+
self.react_perf_plugin
343+
.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::REACT_PERF, yes));
344+
self.promise_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::PROMISE, yes));
345+
self.node_plugin.inspect(|yes| plugins.builtin.set(BuiltinLintPlugins::NODE, yes));
344346

345347
// Without this, jest plugins adapted to vitest will not be enabled.
346348
if self.vitest_plugin.is_enabled() && self.jest_plugin.is_not_set() {
347-
plugins.set(LintPlugins::JEST, true);
349+
plugins.builtin.set(BuiltinLintPlugins::JEST, true);
348350
}
349351
}
350352
}

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: 40 additions & 21 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, ConfigBuilderError, ConfigStore, ConfigStoreBuilder, ExternalLinter,
17+
InvalidFilterKind, LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
1818
};
1919
use rustc_hash::{FxHashMap, FxHashSet};
2020
use serde_json::Value;
@@ -29,16 +29,22 @@ 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 {
47+
println!("LintRunner::run");
4248
let format_str = self.options.output_options.format;
4349
let output_formatter = OutputFormatter::new(format_str);
4450

@@ -59,10 +65,12 @@ impl Runner for LintRunner {
5965
enable_plugins,
6066
misc_options,
6167
disable_nested_config,
62-
inline_config_options,
68+
ref inline_config_options,
6369
..
6470
} = self.options;
6571

72+
let external_linter = self.external_linter.as_ref();
73+
6674
let search_for_nested_configs = !disable_nested_config &&
6775
// If the `--config` option is explicitly passed, we should not search for nested config files
6876
// as the passed config file takes absolute precedence.
@@ -173,7 +181,7 @@ impl Runner for LintRunner {
173181
let handler = GraphicalReportHandler::new();
174182

175183
let nested_configs = if search_for_nested_configs {
176-
match Self::get_nested_configs(stdout, &handler, &filters, &paths) {
184+
match Self::get_nested_configs(stdout, &handler, &filters, &paths, external_linter) {
177185
Ok(v) => v,
178186
Err(v) => return v,
179187
}
@@ -192,20 +200,22 @@ impl Runner for LintRunner {
192200
} else {
193201
None
194202
};
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;
203+
let config_builder =
204+
match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, self.external_linter.as_ref())
205+
{
206+
Ok(builder) => builder,
207+
Err(e) => {
208+
print_and_flush_stdout(
209+
stdout,
210+
&format!(
211+
"Failed to parse configuration file.\n{}\n",
212+
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
213+
),
214+
);
215+
return CliRunResult::InvalidOptionConfig;
216+
}
206217
}
207-
}
208-
.with_filters(&filters);
218+
.with_filters(&filters);
209219

210220
if let Some(basic_config_file) = oxlintrc_for_print {
211221
let config_file = config_builder.resolve_final_config_file(basic_config_file);
@@ -387,7 +397,9 @@ impl LintRunner {
387397
handler: &GraphicalReportHandler,
388398
filters: &Vec<LintFilter>,
389399
paths: &Vec<Arc<OsStr>>,
400+
external_linter: Option<&ExternalLinter>,
390401
) -> Result<FxHashMap<PathBuf, Config>, CliRunResult> {
402+
println!("LintRunner::get_nested_configs");
391403
// TODO(perf): benchmark whether or not it is worth it to store the configurations on a
392404
// per-file or per-directory basis, to avoid calling `.parent()` on every path.
393405
let mut nested_oxlintrc = FxHashMap::<&Path, Oxlintrc>::default();
@@ -417,14 +429,20 @@ impl LintRunner {
417429
// iterate over each config and build the ConfigStore
418430
for (dir, oxlintrc) in nested_oxlintrc {
419431
// TODO(refactor): clean up all of the error handling in this function
420-
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
432+
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter)
433+
{
421434
Ok(builder) => builder,
422435
Err(e) => {
436+
let err = if let ConfigBuilderError::OxcDiagnostic(e) = e {
437+
e
438+
} else {
439+
OxcDiagnostic::error(e.to_string())
440+
};
423441
print_and_flush_stdout(
424442
stdout,
425443
&format!(
426444
"Failed to parse configuration file.\n{}\n",
427-
render_report(handler, &OxcDiagnostic::error(e.to_string()))
445+
render_report(handler, &err)
428446
),
429447
);
430448

@@ -538,6 +556,7 @@ fn check_for_writer_error(error: std::io::Error) -> Result<(), std::io::Error> {
538556
}
539557

540558
fn render_report(handler: &GraphicalReportHandler, diagnostic: &OxcDiagnostic) -> String {
559+
dbg!(diagnostic);
541560
let mut err = String::new();
542561
handler.render_report(&mut err, diagnostic).unwrap();
543562
err

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: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ description.workspace = true
1515

1616
[features]
1717
default = []
18-
ruledocs = ["oxc_macros/ruledocs"] # Enables the `ruledocs` feature for conditional compilation
19-
language_server = ["oxc_data_structures/rope"] # For the Runtime to support needed information for the language server
18+
ruledocs = [
19+
"oxc_macros/ruledocs",
20+
] # Enables the `ruledocs` feature for conditional compilation
21+
language_server = [
22+
"oxc_data_structures/rope",
23+
] # For the Runtime to support needed information for the language server
2024

2125
[lints]
2226
workspace = true
@@ -48,6 +52,7 @@ constcat = { workspace = true }
4852
convert_case = { workspace = true }
4953
cow-utils = { workspace = true }
5054
fast-glob = { workspace = true }
55+
futures = { workspace = true }
5156
globset = { workspace = true }
5257
indexmap = { workspace = true, features = ["rayon"] }
5358
itertools = { workspace = true }
@@ -57,8 +62,11 @@ language-tags = { workspace = true }
5762
lazy-regex = { workspace = true }
5863
lazy_static = { workspace = true }
5964
memchr = { workspace = true }
65+
napi = { workspace = true }
66+
napi-derive = { workspace = true }
6067
nonmax = { workspace = true }
6168
phf = { workspace = true, features = ["macros"] }
69+
tokio = { workspace = true }
6270
rayon = { workspace = true }
6371
rust-lapper = { workspace = true }
6472
rustc-hash = { workspace = true }

0 commit comments

Comments
 (0)