Skip to content

Commit

Permalink
Add exclude support to ruff analyze (#13425)
Browse files Browse the repository at this point in the history
## Summary

Closes #13424.
  • Loading branch information
charliermarsh authored Sep 20, 2024
1 parent 149fb20 commit 910fac7
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 9 deletions.
25 changes: 18 additions & 7 deletions crates/ruff/src/commands/analyze_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{python_files_in_path, ResolvedFile};
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile};
use rustc_hash::FxHashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -74,19 +74,30 @@ pub(crate) fn analyze_graph(
continue;
};

let path = resolved_file.into_path();
let path = resolved_file.path();
let package = path
.parent()
.and_then(|parent| package_roots.get(parent))
.and_then(Clone::clone);

// Resolve the per-file settings.
let settings = resolver.resolve(&path);
let settings = resolver.resolve(path);
let string_imports = settings.analyze.detect_string_imports;
let include_dependencies = settings.analyze.include_dependencies.get(&path).cloned();
let include_dependencies = settings.analyze.include_dependencies.get(path).cloned();

// Skip excluded files.
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
&& match_exclusion(
resolved_file.path(),
resolved_file.file_name(),
&settings.analyze.exclude,
)
{
continue;
}

// Ignore non-Python files.
let source_type = match settings.analyze.extension.get(&path) {
let source_type = match settings.analyze.extension.get(path) {
None => match SourceType::from(&path) {
SourceType::Python(source_type) => source_type,
SourceType::Toml(_) => {
Expand All @@ -106,7 +117,7 @@ pub(crate) fn analyze_graph(
warn!("Failed to convert package to system path");
continue;
};
let Ok(path) = SystemPathBuf::from_path_buf(path) else {
let Ok(path) = SystemPathBuf::from_path_buf(resolved_file.into_path()) else {
warn!("Failed to convert path to system path");
continue;
};
Expand All @@ -118,7 +129,7 @@ pub(crate) fn analyze_graph(
scope.spawn(move |_| {
// Identify any imports via static analysis.
let mut imports =
ModuleImports::detect(&path, package.as_deref(), string_imports, &db)
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
Expand Down
41 changes: 41 additions & 0 deletions crates/ruff/tests/analyze_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,44 @@ fn globs() -> Result<()> {

Ok(())
}

#[test]
fn exclude() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());

root.child("ruff.toml").write_str(indoc::indoc! {r#"
[analyze]
exclude = ["ruff/c.py"]
"#})?;

root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
import ruff.b
"#})?;
root.child("ruff").child("b.py").write_str("")?;
root.child("ruff").child("c.py").write_str("")?;

insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": []
}
----- stderr -----
"###);
});

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ formatter.docstring_code_format = disabled
formatter.docstring_code_line_width = dynamic

# Analyze Settings
analyze.exclude = []
analyze.preview = disabled
analyze.detect_string_imports = false
analyze.extension = ExtensionMapping({})
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ pub struct ModuleImports(BTreeSet<SystemPathBuf>);
impl ModuleImports {
/// Detect the [`ModuleImports`] for a given Python file.
pub fn detect(
db: &ModuleDb,
path: &SystemPath,
package: Option<&SystemPath>,
string_imports: bool,
db: &ModuleDb,
) -> Result<Self> {
// Read and parse the source code.
let file = system_path_to_file(db, path)?;
Expand Down
4 changes: 3 additions & 1 deletion crates/ruff_graph/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use ruff_linter::display_settings;
use ruff_linter::settings::types::{ExtensionMapping, PreviewMode};
use ruff_linter::settings::types::{ExtensionMapping, FilePatternSet, PreviewMode};
use ruff_macros::CacheKey;
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;

#[derive(Debug, Default, Clone, CacheKey)]
pub struct AnalyzeSettings {
pub exclude: FilePatternSet,
pub preview: PreviewMode,
pub detect_string_imports: bool,
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
Expand All @@ -20,6 +21,7 @@ impl fmt::Display for AnalyzeSettings {
formatter = f,
namespace = "analyze",
fields = [
self.exclude,
self.preview,
self.detect_string_imports,
self.extension | debug,
Expand Down
13 changes: 13 additions & 0 deletions crates/ruff_workspace/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ impl Configuration {
let analyze_defaults = AnalyzeSettings::default();

let analyze = AnalyzeSettings {
exclude: FilePatternSet::try_from_iter(analyze.exclude.unwrap_or_default())?,
preview: analyze_preview,
extension: self.extension.clone().unwrap_or_default(),
detect_string_imports: analyze
Expand Down Expand Up @@ -1218,7 +1219,9 @@ impl FormatConfiguration {

#[derive(Clone, Debug, Default)]
pub struct AnalyzeConfiguration {
pub exclude: Option<Vec<FilePattern>>,
pub preview: Option<PreviewMode>,

pub direction: Option<Direction>,
pub detect_string_imports: Option<bool>,
pub include_dependencies: Option<BTreeMap<PathBuf, (PathBuf, Vec<String>)>>,
Expand All @@ -1228,6 +1231,15 @@ impl AnalyzeConfiguration {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: AnalyzeOptions, project_root: &Path) -> Result<Self> {
Ok(Self {
exclude: options.exclude.map(|paths| {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
}),
preview: options.preview.map(PreviewMode::from),
direction: options.direction,
detect_string_imports: options.detect_string_imports,
Expand All @@ -1246,6 +1258,7 @@ impl AnalyzeConfiguration {
#[allow(clippy::needless_pass_by_value)]
pub fn combine(self, config: Self) -> Self {
Self {
exclude: self.exclude.or(config.exclude),
preview: self.preview.or(config.preview),
direction: self.direction.or(config.direction),
detect_string_imports: self.detect_string_imports.or(config.detect_string_imports),
Expand Down
21 changes: 21 additions & 0 deletions crates/ruff_workspace/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3320,6 +3320,27 @@ pub struct FormatOptions {
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AnalyzeOptions {
/// A list of file patterns to exclude from analysis in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).
///
/// Exclusions are based on globs, and can be either:
///
/// - Single-path patterns, like `.mypy_cache` (to exclude any directory
/// named `.mypy_cache` in the tree), `foo.py` (to exclude any file named
/// `foo.py`), or `foo_*.py` (to exclude any file matching `foo_*.py` ).
/// - Relative patterns, like `directory/foo.py` (to exclude that specific
/// file) or `directory/*.py` (to exclude any Python files in
/// `directory`). Note that these paths are relative to the project root
/// (e.g., the directory containing your `pyproject.toml`).
///
/// For more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).
#[option(
default = r#"[]"#,
value_type = "list[str]",
example = r#"
exclude = ["generated"]
"#
)]
pub exclude: Option<Vec<String>>,
/// Whether to enable preview mode. When preview mode is enabled, Ruff will expose unstable
/// commands.
#[option(
Expand Down
10 changes: 10 additions & 0 deletions ruff.schema.json

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

0 comments on commit 910fac7

Please sign in to comment.