From 34a5063aa215f0caeaeb98017009dce9c83be50f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 27 May 2024 12:59:46 -0400 Subject: [PATCH] Respect excludes in `ruff server` configuration discovery (#11551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Right now, we're discovering configuration files even within (e.g.) virtual environments, because we're recursing without respecting the `exclude` field on parent configuration. Closes https://github.com/astral-sh/ruff-vscode/issues/478. ## Test Plan Installed Pandas; verified that I saw no warnings: ![Screenshot 2024-05-26 at 8 09 05 PM](https://github.com/astral-sh/ruff/assets/1309177/dcf4115c-d7b3-453b-b7c7-afdd4804d6f5) --- Cargo.lock | 1 + crates/ruff_server/Cargo.toml | 1 + .../src/session/index/ruff_settings.rs | 77 ++++++++++++++----- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c046fdac0f2de..96e0ebde771bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2367,6 +2367,7 @@ version = "0.2.2" dependencies = [ "anyhow", "crossbeam", + "globset", "insta", "jod-thread", "libc", diff --git a/crates/ruff_server/Cargo.toml b/crates/ruff_server/Cargo.toml index ad5fca9392cdb..bb0c2432ccd3d 100644 --- a/crates/ruff_server/Cargo.toml +++ b/crates/ruff_server/Cargo.toml @@ -28,6 +28,7 @@ ruff_workspace = { workspace = true } anyhow = { workspace = true } crossbeam = { workspace = true } +globset = { workspace = true } jod-thread = { workspace = true } lsp-server = { workspace = true } lsp-types = { workspace = true } diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs index 628b6eddce9b9..aba65e9afaccf 100644 --- a/crates/ruff_server/src/session/index/ruff_settings.rs +++ b/crates/ruff_server/src/session/index/ruff_settings.rs @@ -1,7 +1,9 @@ +use globset::Candidate; use ruff_linter::{ display_settings, fs::normalize_path_to, settings::types::FilePattern, settings::types::PreviewMode, }; +use ruff_workspace::resolver::match_candidate_exclusion; use ruff_workspace::{ configuration::{Configuration, FormatConfiguration, LintConfiguration, RuleSelection}, pyproject::{find_user_settings_toml, settings_toml}, @@ -12,12 +14,13 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use walkdir::{DirEntry, WalkDir}; +use walkdir::WalkDir; use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; -#[derive(Default)] pub(crate) struct RuffSettings { + /// Settings used to manage file inclusion and exclusion. + file_resolver: ruff_workspace::FileResolverSettings, /// Settings to pass into the Ruff linter. linter: ruff_linter::settings::LinterSettings, /// Settings to pass into the Ruff formatter. @@ -54,7 +57,7 @@ impl RuffSettings { .ok() }) .unwrap_or_else(|| { - let default_configuration = ruff_workspace::configuration::Configuration::default(); + let default_configuration = Configuration::default(); EditorConfigurationTransformer(editor_settings, root) .transform(default_configuration) .into_settings(root) @@ -64,6 +67,7 @@ impl RuffSettings { }); RuffSettings { + file_resolver: fallback.file_resolver, formatter: fallback.formatter, linter: fallback.linter, } @@ -85,10 +89,6 @@ impl RuffSettingsIndex { // Add any settings from above the workspace root. for directory in root.ancestors() { if let Some(pyproject) = settings_toml(directory).ok().flatten() { - if index.contains_key(&pyproject) { - continue; - } - let Ok(settings) = ruff_workspace::resolver::resolve_root_settings( &pyproject, Relativity::Parent, @@ -96,9 +96,11 @@ impl RuffSettingsIndex { ) else { continue; }; + index.insert( directory.to_path_buf(), Arc::new(RuffSettings { + file_resolver: settings.file_resolver, linter: settings.linter, formatter: settings.formatter, }), @@ -107,18 +109,55 @@ impl RuffSettingsIndex { } } - // Add any settings within the workspace itself. - for directory in WalkDir::new(root) - .into_iter() - .filter_map(Result::ok) - .filter(|entry| entry.file_type().is_dir()) - .map(DirEntry::into_path) - { - if let Some(pyproject) = settings_toml(&directory).ok().flatten() { - if index.contains_key(&pyproject) { - continue; + // Add any settings within the workspace itself + let mut walker = WalkDir::new(root).into_iter(); + + while let Some(entry) = walker.next() { + let Ok(entry) = entry else { + continue; + }; + + // Skip non-directories. + if !entry.file_type().is_dir() { + continue; + } + + let directory = entry.into_path(); + + // If the directory is excluded from the workspace, skip it. + if let Some(file_name) = directory.file_name() { + if let Some((_, settings)) = index + .range(..directory.clone()) + .rfind(|(path, _)| directory.starts_with(path)) + { + let candidate = Candidate::new(&directory); + let basename = Candidate::new(file_name); + if match_candidate_exclusion( + &candidate, + &basename, + &settings.file_resolver.exclude, + ) { + tracing::debug!("Ignored path via `exclude`: {}", directory.display()); + + walker.skip_current_dir(); + continue; + } else if match_candidate_exclusion( + &candidate, + &basename, + &settings.file_resolver.extend_exclude, + ) { + tracing::debug!( + "Ignored path via `extend-exclude`: {}", + directory.display() + ); + + walker.skip_current_dir(); + continue; + } } + } + if let Some(pyproject) = settings_toml(&directory).ok().flatten() { let Ok(settings) = ruff_workspace::resolver::resolve_root_settings( &pyproject, Relativity::Parent, @@ -129,6 +168,7 @@ impl RuffSettingsIndex { index.insert( directory, Arc::new(RuffSettings { + file_resolver: settings.file_resolver, linter: settings.linter, formatter: settings.formatter, }), @@ -145,8 +185,7 @@ impl RuffSettingsIndex { if let Some((_, settings)) = self .index .range(..document_path.to_path_buf()) - .rev() - .find(|(path, _)| document_path.starts_with(path)) + .rfind(|(path, _)| document_path.starts_with(path)) { return settings.clone(); }