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
32 changes: 9 additions & 23 deletions apps/oxlint/src/js_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,29 +106,15 @@ fn parse_js_config_response(json: &str) -> Result<Vec<JsConfigResult>, Vec<OxcDi

oxlintrc.path.clone_from(&path);

if let Some(config_dir) = oxlintrc.path.parent() {
if let Some(external_plugins) = &mut oxlintrc.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}

for override_config in oxlintrc.overrides.iter_mut() {
if let Some(external_plugins) = &mut override_config.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}
}
}
let Some(config_dir_parent) = oxlintrc.path.parent() else {
errors.push(OxcDiagnostic::error(format!(
"Config path has no parent directory: {}",
entry.path
)));
return (configs, errors);
};
let config_dir = config_dir_parent.to_path_buf();
oxlintrc.set_config_dir(&config_dir);
configs.push(JsConfigResult { path, config: oxlintrc });

(configs, errors)
Expand Down
97 changes: 75 additions & 22 deletions crates/oxc_linter/src/config/oxlintrc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,28 +171,9 @@ impl Oxlintrc {
config.path = path.to_path_buf();

#[expect(clippy::missing_panics_doc)]
let config_dir = config.path.parent().unwrap();
if let Some(external_plugins) = &mut config.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}

for override_config in config.overrides.iter_mut() {
if let Some(external_plugins) = &mut override_config.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}
}
let config_dir =
config.path.parent().expect("config path should have a parent directory").to_path_buf();
config.set_config_dir(&config_dir);

Ok(config)
}
Expand Down Expand Up @@ -278,6 +259,34 @@ impl Oxlintrc {
extends: self.extends.clone(),
}
}

/// Update the configuration directory for all external plugin entries in this config
/// and in its overrides. The underlying `HashSet` is rebuilt because `config_dir`
/// participates in the `Hash`/`Eq` implementation of each entry, so changing it
/// requires rehashing the set.
pub fn set_config_dir(&mut self, config_dir: &Path) {
if let Some(external_plugins) = &mut self.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}

for override_config in self.overrides.iter_mut() {
if let Some(external_plugins) = &mut override_config.external_plugins {
*external_plugins = std::mem::take(external_plugins)
.into_iter()
.map(|mut entry| {
entry.config_dir = config_dir.to_path_buf();
entry
})
.collect();
}
}
}
}

fn is_json_ext(ext: &str) -> bool {
Expand Down Expand Up @@ -456,4 +465,48 @@ mod test {
let merged = config1.merge(config2);
assert_eq!(merged.schema, Some("schema2.json".to_string()));
}

#[test]
fn test_set_config_dir() {
let mut config: Oxlintrc = serde_json::from_str(
r#"{
"jsPlugins": ["./plugin1.ts", { "name": "custom", "specifier": "./plugin2.ts" }],
"overrides": [{ "files": ["*.test.ts"], "jsPlugins": ["./override-plugin.ts"] }]
}"#,
)
.unwrap();

// Verify initial state - config_dir should be empty PathBuf
let top_level_plugins = config.external_plugins.as_ref().unwrap();
assert_eq!(top_level_plugins.len(), 2);
for entry in top_level_plugins {
assert_eq!(entry.config_dir, PathBuf::new());
}

let override_plugins =
config.overrides.iter().next().unwrap().external_plugins.as_ref().unwrap();
assert_eq!(override_plugins.len(), 1);
for entry in override_plugins {
assert_eq!(entry.config_dir, PathBuf::new());
}

// Call set_config_dir
let new_config_dir = PathBuf::from("/project/config");
config.set_config_dir(&new_config_dir);

// Assert that all top-level plugins have the new config_dir
let top_level_plugins = config.external_plugins.as_ref().unwrap();
assert_eq!(top_level_plugins.len(), 2);
for entry in top_level_plugins {
assert_eq!(entry.config_dir, new_config_dir);
}

// Assert that all override plugins have the new config_dir
let override_plugins =
config.overrides.iter().next().unwrap().external_plugins.as_ref().unwrap();
assert_eq!(override_plugins.len(), 1);
for entry in override_plugins {
assert_eq!(entry.config_dir, new_config_dir);
}
}
}
Loading