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
15 changes: 12 additions & 3 deletions apps/oxlint/src/js_plugins/external_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ pub fn create_external_linter(
ExternalLinter::new(rust_load_plugin, rust_setup_configs, rust_lint_file)
}

/// Result returned by `loadPlugin` JS callback.
#[derive(Clone, Debug, Deserialize)]
pub enum LoadPluginReturnValue {
Success(LoadPluginResult),
Failure(String),
}

/// Wrap `loadPlugin` JS callback as a normal Rust function.
///
/// The JS-side function is async. The returned Rust function blocks the current thread
Expand All @@ -48,9 +55,11 @@ fn wrap_load_plugin(cb: JsLoadPluginCb) -> ExternalLinterLoadPluginCb {

match res {
// `loadPlugin` returns JSON string if plugin loaded successfully, or an error occurred
Ok(json) => match serde_json::from_str::<LoadPluginResult>(&json) {
// Plugin loaded successfully, or error occurred on JS side
Ok(result) => Ok(result),
Ok(json) => match serde_json::from_str(&json) {
// Plugin loaded successfully
Ok(LoadPluginReturnValue::Success(result)) => Ok(result),
// Error occurred on JS side
Ok(LoadPluginReturnValue::Failure(err)) => Err(err),
// Invalid JSON - should be impossible, because we control serialization on JS side
Err(err) => {
Err(format!("Failed to deserialize JSON returned by `loadPlugin`: {err}"))
Expand Down
43 changes: 16 additions & 27 deletions crates/oxc_linter/src/config/config_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use crate::{
AllowWarnDeny, ExternalPluginStore, LintConfig, LintFilter, LintFilterKind, Oxlintrc,
RuleCategory, RuleEnum,
config::{
ESLintRule, OxlintOverrides, OxlintRules, overrides::OxlintOverride, plugins::LintPlugins,
ESLintRule, OxlintOverrides, OxlintRules,
overrides::OxlintOverride,
plugins::{LintPlugins, normalize_plugin_name},
},
external_linter::ExternalLinter,
external_plugin_store::{ExternalOptionsId, ExternalRuleId, ExternalRuleLookupError},
Expand Down Expand Up @@ -525,8 +527,6 @@ impl ConfigStoreBuilder {
resolver: &Resolver,
external_plugin_store: &mut ExternalPluginStore,
) -> Result<(), ConfigBuilderError> {
use crate::LoadPluginResult;

// Print warning on 1st attempt to load a plugin
#[expect(clippy::print_stderr)]
if external_plugin_store.is_empty() {
Expand Down Expand Up @@ -562,30 +562,19 @@ impl ConfigStoreBuilder {
}
})?;

match result {
LoadPluginResult::Success { name, offset, rule_names } => {
// Normalize plugin name (e.g., "eslint-plugin-foo" -> "foo", "@foo/eslint-plugin" -> "@foo")
use crate::config::plugins::normalize_plugin_name;
let normalized_name = normalize_plugin_name(&name).into_owned();

if LintPlugins::try_from(normalized_name.as_str()).is_err() {
external_plugin_store.register_plugin(
plugin_path,
normalized_name,
offset,
rule_names,
);
Ok(())
} else {
Err(ConfigBuilderError::ReservedExternalPluginName {
plugin_name: normalized_name,
})
}
}
LoadPluginResult::Failure(e) => Err(ConfigBuilderError::PluginLoadFailed {
plugin_specifier: plugin_specifier.to_string(),
error: e,
}),
// Normalize plugin name (e.g., "eslint-plugin-foo" -> "foo", "@foo/eslint-plugin" -> "@foo")
let plugin_name = normalize_plugin_name(&result.name).into_owned();

if LintPlugins::try_from(plugin_name.as_str()).is_err() {
external_plugin_store.register_plugin(
plugin_path,
plugin_name,
result.offset,
result.rule_names,
);
Ok(())
} else {
Err(ConfigBuilderError::ReservedExternalPluginName { plugin_name })
}
}
}
Expand Down
13 changes: 5 additions & 8 deletions crates/oxc_linter/src/external_linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ pub type ExternalLinterLintFileCb = Box<
>;

#[derive(Clone, Debug, Deserialize)]
pub enum LoadPluginResult {
#[serde(rename_all = "camelCase")]
Success {
name: String,
offset: usize,
rule_names: Vec<String>,
},
Failure(String),
#[serde(rename_all = "camelCase")]
pub struct LoadPluginResult {
pub name: String,
pub offset: usize,
pub rule_names: Vec<String>,
}

#[derive(Clone, Debug, Deserialize)]
Expand Down
Loading