Respect excludes in ruff server configuration discovery (#11551)

## 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](dcf4115c-d7b3-453b-b7c7-afdd4804d6f5)
This commit is contained in:
Charlie Marsh 2024-05-27 12:59:46 -04:00 committed by GitHub
parent adc0a5d126
commit 34a5063aa2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 61 additions and 20 deletions

1
Cargo.lock generated
View file

@ -2367,6 +2367,7 @@ version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossbeam", "crossbeam",
"globset",
"insta", "insta",
"jod-thread", "jod-thread",
"libc", "libc",

View file

@ -28,6 +28,7 @@ ruff_workspace = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
crossbeam = { workspace = true } crossbeam = { workspace = true }
globset = { workspace = true }
jod-thread = { workspace = true } jod-thread = { workspace = true }
lsp-server = { workspace = true } lsp-server = { workspace = true }
lsp-types = { workspace = true } lsp-types = { workspace = true }

View file

@ -1,7 +1,9 @@
use globset::Candidate;
use ruff_linter::{ use ruff_linter::{
display_settings, fs::normalize_path_to, settings::types::FilePattern, display_settings, fs::normalize_path_to, settings::types::FilePattern,
settings::types::PreviewMode, settings::types::PreviewMode,
}; };
use ruff_workspace::resolver::match_candidate_exclusion;
use ruff_workspace::{ use ruff_workspace::{
configuration::{Configuration, FormatConfiguration, LintConfiguration, RuleSelection}, configuration::{Configuration, FormatConfiguration, LintConfiguration, RuleSelection},
pyproject::{find_user_settings_toml, settings_toml}, pyproject::{find_user_settings_toml, settings_toml},
@ -12,12 +14,13 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use walkdir::{DirEntry, WalkDir}; use walkdir::WalkDir;
use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings};
#[derive(Default)]
pub(crate) struct RuffSettings { pub(crate) struct RuffSettings {
/// Settings used to manage file inclusion and exclusion.
file_resolver: ruff_workspace::FileResolverSettings,
/// Settings to pass into the Ruff linter. /// Settings to pass into the Ruff linter.
linter: ruff_linter::settings::LinterSettings, linter: ruff_linter::settings::LinterSettings,
/// Settings to pass into the Ruff formatter. /// Settings to pass into the Ruff formatter.
@ -54,7 +57,7 @@ impl RuffSettings {
.ok() .ok()
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| {
let default_configuration = ruff_workspace::configuration::Configuration::default(); let default_configuration = Configuration::default();
EditorConfigurationTransformer(editor_settings, root) EditorConfigurationTransformer(editor_settings, root)
.transform(default_configuration) .transform(default_configuration)
.into_settings(root) .into_settings(root)
@ -64,6 +67,7 @@ impl RuffSettings {
}); });
RuffSettings { RuffSettings {
file_resolver: fallback.file_resolver,
formatter: fallback.formatter, formatter: fallback.formatter,
linter: fallback.linter, linter: fallback.linter,
} }
@ -85,10 +89,6 @@ impl RuffSettingsIndex {
// Add any settings from above the workspace root. // Add any settings from above the workspace root.
for directory in root.ancestors() { for directory in root.ancestors() {
if let Some(pyproject) = settings_toml(directory).ok().flatten() { if let Some(pyproject) = settings_toml(directory).ok().flatten() {
if index.contains_key(&pyproject) {
continue;
}
let Ok(settings) = ruff_workspace::resolver::resolve_root_settings( let Ok(settings) = ruff_workspace::resolver::resolve_root_settings(
&pyproject, &pyproject,
Relativity::Parent, Relativity::Parent,
@ -96,9 +96,11 @@ impl RuffSettingsIndex {
) else { ) else {
continue; continue;
}; };
index.insert( index.insert(
directory.to_path_buf(), directory.to_path_buf(),
Arc::new(RuffSettings { Arc::new(RuffSettings {
file_resolver: settings.file_resolver,
linter: settings.linter, linter: settings.linter,
formatter: settings.formatter, formatter: settings.formatter,
}), }),
@ -107,18 +109,55 @@ impl RuffSettingsIndex {
} }
} }
// Add any settings within the workspace itself. // Add any settings within the workspace itself
for directory in WalkDir::new(root) let mut walker = WalkDir::new(root).into_iter();
.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;
}
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( let Ok(settings) = ruff_workspace::resolver::resolve_root_settings(
&pyproject, &pyproject,
Relativity::Parent, Relativity::Parent,
@ -129,6 +168,7 @@ impl RuffSettingsIndex {
index.insert( index.insert(
directory, directory,
Arc::new(RuffSettings { Arc::new(RuffSettings {
file_resolver: settings.file_resolver,
linter: settings.linter, linter: settings.linter,
formatter: settings.formatter, formatter: settings.formatter,
}), }),
@ -145,8 +185,7 @@ impl RuffSettingsIndex {
if let Some((_, settings)) = self if let Some((_, settings)) = self
.index .index
.range(..document_path.to_path_buf()) .range(..document_path.to_path_buf())
.rev() .rfind(|(path, _)| document_path.starts_with(path))
.find(|(path, _)| document_path.starts_with(path))
{ {
return settings.clone(); return settings.clone();
} }