Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ rustflags = [
# See https://x.com/Brooooook_lyn/status/1895848334692401270
[target.'cfg(target_env = "gnu")']
rustflags = ["-C", "link-args=-Wl,-z,nodelete"]

[target.'cfg(all(target_os = "linux", target_env = "gnu"))']
rustflags = [
"-C",
"link-args=-Wl,--warn-unresolved-symbols",
"-C",
"link-args=-Wl,-z,nodelete",
]
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions apps/oxlint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ pub mod cli {
pub use crate::{command::*, lint::LintRunner, result::CliRunResult, runner::Runner};
}

pub use oxc_linter::{ExternalLinter, ExternalLinterCb, ExternalLinterLoadPluginCb};

#[cfg(all(feature = "allocator", not(miri), not(target_family = "wasm")))]
#[global_allocator]
static GLOBAL: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;

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

pub fn lint() -> CliRunResult {
pub fn lint(external_linter: Option<ExternalLinter>) -> CliRunResult {
init_tracing();
init_miette();

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

LintRunner::new(command).run(&mut stdout)
LintRunner::new(command, external_linter).run(&mut stdout)
}

// Initialize the data which relies on `is_atty` system calls so they don't block subsequent threads.
Expand Down
48 changes: 29 additions & 19 deletions apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
use oxc_allocator::AllocatorPool;
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
use oxc_linter::{
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter,
LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, ExternalLinter, InvalidFilterKind,
LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
};
use rustc_hash::{FxHashMap, FxHashSet};
use serde_json::Value;
Expand All @@ -29,13 +29,18 @@ use crate::{
pub struct LintRunner {
options: LintCommand,
cwd: PathBuf,
external_linter: Option<ExternalLinter>,
}

impl Runner for LintRunner {
type Options = LintCommand;

fn new(options: Self::Options) -> Self {
Self { options, cwd: env::current_dir().expect("Failed to get current working directory") }
fn new(options: Self::Options, external_linter: Option<ExternalLinter>) -> Self {
Self {
options,
cwd: env::current_dir().expect("Failed to get current working directory"),
external_linter,
}
}

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

let external_linter = self.external_linter.as_ref();

let search_for_nested_configs = !disable_nested_config &&
// If the `--config` option is explicitly passed, we should not search for nested config files
// as the passed config file takes absolute precedence.
Expand Down Expand Up @@ -173,7 +180,7 @@ impl Runner for LintRunner {
let handler = GraphicalReportHandler::new();

let nested_configs = if search_for_nested_configs {
match Self::get_nested_configs(stdout, &handler, &filters, &paths) {
match Self::get_nested_configs(stdout, &handler, &filters, &paths, external_linter) {
Ok(v) => v,
Err(v) => return v,
}
Expand All @@ -192,20 +199,21 @@ impl Runner for LintRunner {
} else {
None
};
let config_builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
Ok(builder) => builder,
Err(e) => {
print_and_flush_stdout(
stdout,
&format!(
"Failed to parse configuration file.\n{}\n",
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
),
);
return CliRunResult::InvalidOptionConfig;
let config_builder =
match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter) {
Ok(builder) => builder,
Err(e) => {
print_and_flush_stdout(
stdout,
&format!(
"Failed to parse configuration file.\n{}\n",
render_report(&handler, &OxcDiagnostic::error(e.to_string()))
),
);
return CliRunResult::InvalidOptionConfig;
}
}
}
.with_filters(&filters);
.with_filters(&filters);

if let Some(basic_config_file) = oxlintrc_for_print {
let config_file = config_builder.resolve_final_config_file(basic_config_file);
Expand Down Expand Up @@ -387,6 +395,7 @@ impl LintRunner {
handler: &GraphicalReportHandler,
filters: &Vec<LintFilter>,
paths: &Vec<Arc<OsStr>>,
external_linter: Option<&ExternalLinter>,
) -> Result<FxHashMap<PathBuf, Config>, CliRunResult> {
// TODO(perf): benchmark whether or not it is worth it to store the configurations on a
// per-file or per-directory basis, to avoid calling `.parent()` on every path.
Expand Down Expand Up @@ -417,7 +426,8 @@ impl LintRunner {
// iterate over each config and build the ConfigStore
for (dir, oxlintrc) in nested_oxlintrc {
// TODO(refactor): clean up all of the error handling in this function
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc) {
let builder = match ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, external_linter)
{
Ok(builder) => builder,
Err(e) => {
print_and_flush_stdout(
Expand Down
2 changes: 1 addition & 1 deletion apps/oxlint/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use oxlint::{cli::CliRunResult, lint};

fn main() -> CliRunResult {
lint()
lint(None)
}
4 changes: 3 additions & 1 deletion apps/oxlint/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::io::Write;

use oxc_linter::ExternalLinter;

use crate::cli::CliRunResult;

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

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

/// Executes the runner, providing some result to the CLI.
fn run(self, stdout: &mut dyn Write) -> CliRunResult;
Expand Down
4 changes: 2 additions & 2 deletions apps/oxlint/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl Tester {

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

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

output.extend_from_slice(b"----------\n");
output.extend_from_slice(format!("CLI result: {result:?}\n").as_bytes());
Expand Down
4 changes: 2 additions & 2 deletions crates/oxc_language_server/src/linter/server_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl ServerLinter {

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

// TODO(refactor): pull this into a shared function, because in oxlint we have the same functionality.
let use_nested_config = options.use_nested_configs();
Expand Down Expand Up @@ -131,7 +131,7 @@ impl ServerLinter {
warn!("Skipping invalid config file: {}", file_path.display());
continue;
};
let Ok(config_store_builder) = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc)
let Ok(config_store_builder) = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, None)
else {
warn!("Skipping config (builder failed): {}", file_path.display());
continue;
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ language-tags = { workspace = true }
lazy-regex = { workspace = true }
lazy_static = { workspace = true }
memchr = { workspace = true }
napi = { workspace = true }
napi-derive = { workspace = true }
nonmax = { workspace = true }
phf = { workspace = true, features = ["macros"] }
rayon = { workspace = true }
Expand Down
21 changes: 15 additions & 6 deletions crates/oxc_linter/src/config/config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use oxc_span::{CompactStr, format_compact_str};
use crate::{
AllowWarnDeny, LintConfig, LintFilter, LintFilterKind, Oxlintrc, RuleCategory, RuleEnum,
config::{ESLintRule, LintPlugins, OxlintOverrides, OxlintRules, overrides::OxlintOverride},
external_linter::ExternalLinter,
rules::RULES,
};

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

#[inline]
fn try_from(oxlintrc: Oxlintrc) -> Result<Self, Self::Error> {
Self::from_oxlintrc(false, oxlintrc)
Self::from_oxlintrc(false, oxlintrc, None)
}
}

Expand Down Expand Up @@ -640,7 +642,7 @@ mod test {
"#,
)
.unwrap();
let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc).unwrap();
let builder = ConfigStoreBuilder::from_oxlintrc(false, oxlintrc, None).unwrap();
for (rule, severity) in &builder.rules {
let name = rule.name();
let plugin = rule.plugin_name();
Expand Down Expand Up @@ -815,6 +817,7 @@ mod test {
"fixtures/extends_config/extends_invalid_config.json",
))
.unwrap(),
None,
);
let err = invalid_config.unwrap_err();
assert!(matches!(err, ConfigBuilderError::InvalidConfigFile { .. }));
Expand Down Expand Up @@ -943,12 +946,18 @@ mod test {
}

fn config_store_from_path(path: &str) -> Config {
ConfigStoreBuilder::from_oxlintrc(true, Oxlintrc::from_file(&PathBuf::from(path)).unwrap())
.unwrap()
.build()
ConfigStoreBuilder::from_oxlintrc(
true,
Oxlintrc::from_file(&PathBuf::from(path)).unwrap(),
None,
)
.unwrap()
.build()
}

fn config_store_from_str(s: &str) -> Config {
ConfigStoreBuilder::from_oxlintrc(true, serde_json::from_str(s).unwrap()).unwrap().build()
ConfigStoreBuilder::from_oxlintrc(true, serde_json::from_str(s).unwrap(), None)
.unwrap()
.build()
}
}
37 changes: 37 additions & 0 deletions crates/oxc_linter/src/external_linter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::{fmt::Debug, sync::Arc};

use napi::{Status, bindgen_prelude::Promise, threadsafe_function::ThreadsafeFunction};
use napi_derive::napi;

#[napi]
pub type ExternalLinterCb =
Arc<ThreadsafeFunction<(), /* TODO: correct return type */ (), (), Status, false>>;

#[napi]
pub type ExternalLinterLoadPluginCb =
Arc<ThreadsafeFunction<String, Promise<PluginLoadResult>, String, Status, false>>;

#[napi]
pub enum PluginLoadResult {
Success,
Failure(String),
}

#[derive(Clone)]
#[expect(dead_code)]
pub struct ExternalLinter {
pub(crate) load_plugin: ExternalLinterLoadPluginCb,
pub(crate) run: ExternalLinterCb,
}

impl ExternalLinter {
pub fn new(run: ExternalLinterCb, load_plugin: ExternalLinterLoadPluginCb) -> Self {
ExternalLinter { load_plugin, run }
}
}

impl Debug for ExternalLinter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExternalLinter").finish()
}
}
4 changes: 4 additions & 0 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod ast_util;
mod config;
mod context;
mod disable_directives;
mod external_linter;
mod fixer;
mod frameworks;
mod globals;
Expand All @@ -32,6 +33,9 @@ pub use crate::{
Oxlintrc,
},
context::LintContext,
external_linter::{
ExternalLinter, ExternalLinterCb, ExternalLinterLoadPluginCb, PluginLoadResult,
},
fixer::FixKind,
frameworks::FrameworkFlags,
loader::LINTABLE_EXTENSIONS,
Expand Down
8 changes: 6 additions & 2 deletions crates/oxc_linter/src/tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,12 @@ impl Tester {
eslint_config
.map_or_else(ConfigStoreBuilder::empty, |mut v| {
v.as_object_mut().unwrap().insert("categories".into(), json!({}));
ConfigStoreBuilder::from_oxlintrc(true, Oxlintrc::deserialize(v).unwrap())
.unwrap()
ConfigStoreBuilder::from_oxlintrc(
true,
Oxlintrc::deserialize(v).unwrap(),
None,
)
.unwrap()
})
.with_plugins(self.plugins.union(LintPlugins::from(self.plugin_name)))
.with_rule(rule, AllowWarnDeny::Warn)
Expand Down
11 changes: 10 additions & 1 deletion napi/oxlint2/src/bindings.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/* auto-generated by NAPI-RS */
/* eslint-disable */
export declare function lint(): Promise<boolean>
export type ExternalLinterCb =
(() => void)

export type ExternalLinterLoadPluginCb =
((arg: string) => Promise<PluginLoadResult>)

export type PluginLoadResult =
| { type: 'Success' }
| { type: 'Failure', field0: string }
export declare function lint(loadPlugin: ExternalLinterLoadPluginCb, run: ExternalLinterCb): Promise<boolean>
10 changes: 9 additions & 1 deletion napi/oxlint2/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ import { lint } from './bindings.js';

class Linter {
run() {
return lint();
return lint(this.loadPlugin.bind(this), this.lint.bind(this));
}

loadPlugin = async (_pluginName) => {
throw new Error('unimplemented');
};

lint = async () => {
throw new Error('unimplemented');
};
}

async function main() {
Expand Down
Loading
Loading