mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +00:00
Add exclude
support to ruff analyze
(#13425)
## Summary Closes https://github.com/astral-sh/ruff/issues/13424.
This commit is contained in:
parent
149fb2090e
commit
910fac781d
8 changed files with 108 additions and 9 deletions
|
@ -8,7 +8,7 @@ use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||||
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
|
||||||
use ruff_linter::{warn_user, warn_user_once};
|
use ruff_linter::{warn_user, warn_user_once};
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
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 rustc_hash::FxHashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -74,19 +74,30 @@ pub(crate) fn analyze_graph(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = resolved_file.into_path();
|
let path = resolved_file.path();
|
||||||
let package = path
|
let package = path
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|parent| package_roots.get(parent))
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.and_then(Clone::clone);
|
.and_then(Clone::clone);
|
||||||
|
|
||||||
// Resolve the per-file settings.
|
// Resolve the per-file settings.
|
||||||
let settings = resolver.resolve(&path);
|
let settings = resolver.resolve(path);
|
||||||
let string_imports = settings.analyze.detect_string_imports;
|
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.
|
// 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) {
|
None => match SourceType::from(&path) {
|
||||||
SourceType::Python(source_type) => source_type,
|
SourceType::Python(source_type) => source_type,
|
||||||
SourceType::Toml(_) => {
|
SourceType::Toml(_) => {
|
||||||
|
@ -106,7 +117,7 @@ pub(crate) fn analyze_graph(
|
||||||
warn!("Failed to convert package to system path");
|
warn!("Failed to convert package to system path");
|
||||||
continue;
|
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");
|
warn!("Failed to convert path to system path");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
@ -118,7 +129,7 @@ pub(crate) fn analyze_graph(
|
||||||
scope.spawn(move |_| {
|
scope.spawn(move |_| {
|
||||||
// Identify any imports via static analysis.
|
// Identify any imports via static analysis.
|
||||||
let mut imports =
|
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| {
|
.unwrap_or_else(|err| {
|
||||||
warn!("Failed to generate import map for {path}: {err}");
|
warn!("Failed to generate import map for {path}: {err}");
|
||||||
ModuleImports::default()
|
ModuleImports::default()
|
||||||
|
|
|
@ -261,3 +261,44 @@ fn globs() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
|
@ -389,6 +389,7 @@ formatter.docstring_code_format = disabled
|
||||||
formatter.docstring_code_line_width = dynamic
|
formatter.docstring_code_line_width = dynamic
|
||||||
|
|
||||||
# Analyze Settings
|
# Analyze Settings
|
||||||
|
analyze.exclude = []
|
||||||
analyze.preview = disabled
|
analyze.preview = disabled
|
||||||
analyze.detect_string_imports = false
|
analyze.detect_string_imports = false
|
||||||
analyze.extension = ExtensionMapping({})
|
analyze.extension = ExtensionMapping({})
|
||||||
|
|
|
@ -23,10 +23,10 @@ pub struct ModuleImports(BTreeSet<SystemPathBuf>);
|
||||||
impl ModuleImports {
|
impl ModuleImports {
|
||||||
/// Detect the [`ModuleImports`] for a given Python file.
|
/// Detect the [`ModuleImports`] for a given Python file.
|
||||||
pub fn detect(
|
pub fn detect(
|
||||||
|
db: &ModuleDb,
|
||||||
path: &SystemPath,
|
path: &SystemPath,
|
||||||
package: Option<&SystemPath>,
|
package: Option<&SystemPath>,
|
||||||
string_imports: bool,
|
string_imports: bool,
|
||||||
db: &ModuleDb,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Read and parse the source code.
|
// Read and parse the source code.
|
||||||
let file = system_path_to_file(db, path)?;
|
let file = system_path_to_file(db, path)?;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ruff_linter::display_settings;
|
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 ruff_macros::CacheKey;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -7,6 +7,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, CacheKey)]
|
#[derive(Debug, Default, Clone, CacheKey)]
|
||||||
pub struct AnalyzeSettings {
|
pub struct AnalyzeSettings {
|
||||||
|
pub exclude: FilePatternSet,
|
||||||
pub preview: PreviewMode,
|
pub preview: PreviewMode,
|
||||||
pub detect_string_imports: bool,
|
pub detect_string_imports: bool,
|
||||||
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
|
pub include_dependencies: BTreeMap<PathBuf, (PathBuf, Vec<String>)>,
|
||||||
|
@ -20,6 +21,7 @@ impl fmt::Display for AnalyzeSettings {
|
||||||
formatter = f,
|
formatter = f,
|
||||||
namespace = "analyze",
|
namespace = "analyze",
|
||||||
fields = [
|
fields = [
|
||||||
|
self.exclude,
|
||||||
self.preview,
|
self.preview,
|
||||||
self.detect_string_imports,
|
self.detect_string_imports,
|
||||||
self.extension | debug,
|
self.extension | debug,
|
||||||
|
|
|
@ -215,6 +215,7 @@ impl Configuration {
|
||||||
let analyze_defaults = AnalyzeSettings::default();
|
let analyze_defaults = AnalyzeSettings::default();
|
||||||
|
|
||||||
let analyze = AnalyzeSettings {
|
let analyze = AnalyzeSettings {
|
||||||
|
exclude: FilePatternSet::try_from_iter(analyze.exclude.unwrap_or_default())?,
|
||||||
preview: analyze_preview,
|
preview: analyze_preview,
|
||||||
extension: self.extension.clone().unwrap_or_default(),
|
extension: self.extension.clone().unwrap_or_default(),
|
||||||
detect_string_imports: analyze
|
detect_string_imports: analyze
|
||||||
|
@ -1218,7 +1219,9 @@ impl FormatConfiguration {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct AnalyzeConfiguration {
|
pub struct AnalyzeConfiguration {
|
||||||
|
pub exclude: Option<Vec<FilePattern>>,
|
||||||
pub preview: Option<PreviewMode>,
|
pub preview: Option<PreviewMode>,
|
||||||
|
|
||||||
pub direction: Option<Direction>,
|
pub direction: Option<Direction>,
|
||||||
pub detect_string_imports: Option<bool>,
|
pub detect_string_imports: Option<bool>,
|
||||||
pub include_dependencies: Option<BTreeMap<PathBuf, (PathBuf, Vec<String>)>>,
|
pub include_dependencies: Option<BTreeMap<PathBuf, (PathBuf, Vec<String>)>>,
|
||||||
|
@ -1228,6 +1231,15 @@ impl AnalyzeConfiguration {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn from_options(options: AnalyzeOptions, project_root: &Path) -> Result<Self> {
|
pub fn from_options(options: AnalyzeOptions, project_root: &Path) -> Result<Self> {
|
||||||
Ok(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),
|
preview: options.preview.map(PreviewMode::from),
|
||||||
direction: options.direction,
|
direction: options.direction,
|
||||||
detect_string_imports: options.detect_string_imports,
|
detect_string_imports: options.detect_string_imports,
|
||||||
|
@ -1246,6 +1258,7 @@ impl AnalyzeConfiguration {
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub fn combine(self, config: Self) -> Self {
|
pub fn combine(self, config: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
exclude: self.exclude.or(config.exclude),
|
||||||
preview: self.preview.or(config.preview),
|
preview: self.preview.or(config.preview),
|
||||||
direction: self.direction.or(config.direction),
|
direction: self.direction.or(config.direction),
|
||||||
detect_string_imports: self.detect_string_imports.or(config.detect_string_imports),
|
detect_string_imports: self.detect_string_imports.or(config.detect_string_imports),
|
||||||
|
|
|
@ -3320,6 +3320,27 @@ pub struct FormatOptions {
|
||||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub struct AnalyzeOptions {
|
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
|
/// Whether to enable preview mode. When preview mode is enabled, Ruff will expose unstable
|
||||||
/// commands.
|
/// commands.
|
||||||
#[option(
|
#[option(
|
||||||
|
|
10
ruff.schema.json
generated
10
ruff.schema.json
generated
|
@ -779,6 +779,16 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"exclude": {
|
||||||
|
"description": "A list of file patterns to exclude from analysis in addition to the files excluded globally (see [`exclude`](#exclude), and [`extend-exclude`](#extend-exclude)).\n\nExclusions are based on globs, and can be either:\n\n- 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`).\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"include-dependencies": {
|
"include-dependencies": {
|
||||||
"description": "A map from file path to the list of file paths or globs that should be considered dependencies of that file, regardless of whether relevant imports are detected.",
|
"description": "A map from file path to the list of file paths or globs that should be considered dependencies of that file, regardless of whether relevant imports are detected.",
|
||||||
"type": [
|
"type": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue