mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
Add [format|lint].exclude
options (#8000)
This commit is contained in:
parent
d685107638
commit
fe485d791c
22 changed files with 772 additions and 294 deletions
|
@ -157,6 +157,7 @@ impl Configuration {
|
|||
let format_defaults = FormatterSettings::default();
|
||||
// TODO(micha): Support changing the tab-width but disallow changing the number of spaces
|
||||
let formatter = FormatterSettings {
|
||||
exclude: FilePatternSet::try_from_iter(format.exclude.unwrap_or_default())?,
|
||||
preview: match format.preview.unwrap_or(preview) {
|
||||
PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled,
|
||||
PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled,
|
||||
|
@ -204,6 +205,7 @@ impl Configuration {
|
|||
|
||||
linter: LinterSettings {
|
||||
rules: lint.as_rule_table(preview),
|
||||
exclude: FilePatternSet::try_from_iter(lint.exclude.unwrap_or_default())?,
|
||||
target_version,
|
||||
project_root: project_root.to_path_buf(),
|
||||
allowed_confusables: lint
|
||||
|
@ -365,10 +367,14 @@ impl Configuration {
|
|||
}
|
||||
|
||||
pub fn from_options(options: Options, project_root: &Path) -> Result<Self> {
|
||||
let lint = if let Some(lint) = options.lint {
|
||||
lint.combine(options.lint_top_level)
|
||||
let lint = if let Some(mut lint) = options.lint {
|
||||
lint.common = lint.common.combine(options.lint_top_level);
|
||||
lint
|
||||
} else {
|
||||
options.lint_top_level
|
||||
LintOptions {
|
||||
common: options.lint_top_level,
|
||||
..LintOptions::default()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
@ -455,7 +461,10 @@ impl Configuration {
|
|||
target_version: options.target_version,
|
||||
|
||||
lint: LintConfiguration::from_options(lint, project_root)?,
|
||||
format: FormatConfiguration::from_options(options.format.unwrap_or_default())?,
|
||||
format: FormatConfiguration::from_options(
|
||||
options.format.unwrap_or_default(),
|
||||
project_root,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -501,6 +510,8 @@ impl Configuration {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LintConfiguration {
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
|
||||
// Rule selection
|
||||
pub extend_per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub per_file_ignores: Option<Vec<PerFileIgnore>>,
|
||||
|
@ -550,33 +561,47 @@ pub struct LintConfiguration {
|
|||
impl LintConfiguration {
|
||||
fn from_options(options: LintOptions, project_root: &Path) -> Result<Self> {
|
||||
Ok(LintConfiguration {
|
||||
exclude: options.exclude.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|pattern| {
|
||||
let absolute = fs::normalize_path_to(&pattern, project_root);
|
||||
FilePattern::User(pattern, absolute)
|
||||
})
|
||||
.collect()
|
||||
}),
|
||||
|
||||
rule_selections: vec![RuleSelection {
|
||||
select: options.select,
|
||||
select: options.common.select,
|
||||
ignore: options
|
||||
.common
|
||||
.ignore
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(options.extend_ignore.into_iter().flatten())
|
||||
.chain(options.common.extend_ignore.into_iter().flatten())
|
||||
.collect(),
|
||||
extend_select: options.extend_select.unwrap_or_default(),
|
||||
fixable: options.fixable,
|
||||
extend_select: options.common.extend_select.unwrap_or_default(),
|
||||
fixable: options.common.fixable,
|
||||
unfixable: options
|
||||
.common
|
||||
.unfixable
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(options.extend_unfixable.into_iter().flatten())
|
||||
.chain(options.common.extend_unfixable.into_iter().flatten())
|
||||
.collect(),
|
||||
extend_fixable: options.extend_fixable.unwrap_or_default(),
|
||||
extend_fixable: options.common.extend_fixable.unwrap_or_default(),
|
||||
}],
|
||||
extend_safe_fixes: options.extend_safe_fixes.unwrap_or_default(),
|
||||
extend_unsafe_fixes: options.extend_unsafe_fixes.unwrap_or_default(),
|
||||
allowed_confusables: options.allowed_confusables,
|
||||
extend_safe_fixes: options.common.extend_safe_fixes.unwrap_or_default(),
|
||||
extend_unsafe_fixes: options.common.extend_unsafe_fixes.unwrap_or_default(),
|
||||
allowed_confusables: options.common.allowed_confusables,
|
||||
dummy_variable_rgx: options
|
||||
.common
|
||||
.dummy_variable_rgx
|
||||
.map(|pattern| Regex::new(&pattern))
|
||||
.transpose()
|
||||
.map_err(|e| anyhow!("Invalid `dummy-variable-rgx` value: {e}"))?,
|
||||
extend_per_file_ignores: options
|
||||
.common
|
||||
.extend_per_file_ignores
|
||||
.map(|per_file_ignores| {
|
||||
per_file_ignores
|
||||
|
@ -587,10 +612,10 @@ impl LintConfiguration {
|
|||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
external: options.external,
|
||||
ignore_init_module_imports: options.ignore_init_module_imports,
|
||||
explicit_preview_rules: options.explicit_preview_rules,
|
||||
per_file_ignores: options.per_file_ignores.map(|per_file_ignores| {
|
||||
external: options.common.external,
|
||||
ignore_init_module_imports: options.common.ignore_init_module_imports,
|
||||
explicit_preview_rules: options.common.explicit_preview_rules,
|
||||
per_file_ignores: options.common.per_file_ignores.map(|per_file_ignores| {
|
||||
per_file_ignores
|
||||
.into_iter()
|
||||
.map(|(pattern, prefixes)| {
|
||||
|
@ -598,34 +623,34 @@ impl LintConfiguration {
|
|||
})
|
||||
.collect()
|
||||
}),
|
||||
task_tags: options.task_tags,
|
||||
logger_objects: options.logger_objects,
|
||||
typing_modules: options.typing_modules,
|
||||
task_tags: options.common.task_tags,
|
||||
logger_objects: options.common.logger_objects,
|
||||
typing_modules: options.common.typing_modules,
|
||||
// Plugins
|
||||
flake8_annotations: options.flake8_annotations,
|
||||
flake8_bandit: options.flake8_bandit,
|
||||
flake8_bugbear: options.flake8_bugbear,
|
||||
flake8_builtins: options.flake8_builtins,
|
||||
flake8_comprehensions: options.flake8_comprehensions,
|
||||
flake8_copyright: options.flake8_copyright,
|
||||
flake8_errmsg: options.flake8_errmsg,
|
||||
flake8_gettext: options.flake8_gettext,
|
||||
flake8_implicit_str_concat: options.flake8_implicit_str_concat,
|
||||
flake8_import_conventions: options.flake8_import_conventions,
|
||||
flake8_pytest_style: options.flake8_pytest_style,
|
||||
flake8_quotes: options.flake8_quotes,
|
||||
flake8_self: options.flake8_self,
|
||||
flake8_tidy_imports: options.flake8_tidy_imports,
|
||||
flake8_type_checking: options.flake8_type_checking,
|
||||
flake8_unused_arguments: options.flake8_unused_arguments,
|
||||
isort: options.isort,
|
||||
mccabe: options.mccabe,
|
||||
pep8_naming: options.pep8_naming,
|
||||
pycodestyle: options.pycodestyle,
|
||||
pydocstyle: options.pydocstyle,
|
||||
pyflakes: options.pyflakes,
|
||||
pylint: options.pylint,
|
||||
pyupgrade: options.pyupgrade,
|
||||
flake8_annotations: options.common.flake8_annotations,
|
||||
flake8_bandit: options.common.flake8_bandit,
|
||||
flake8_bugbear: options.common.flake8_bugbear,
|
||||
flake8_builtins: options.common.flake8_builtins,
|
||||
flake8_comprehensions: options.common.flake8_comprehensions,
|
||||
flake8_copyright: options.common.flake8_copyright,
|
||||
flake8_errmsg: options.common.flake8_errmsg,
|
||||
flake8_gettext: options.common.flake8_gettext,
|
||||
flake8_implicit_str_concat: options.common.flake8_implicit_str_concat,
|
||||
flake8_import_conventions: options.common.flake8_import_conventions,
|
||||
flake8_pytest_style: options.common.flake8_pytest_style,
|
||||
flake8_quotes: options.common.flake8_quotes,
|
||||
flake8_self: options.common.flake8_self,
|
||||
flake8_tidy_imports: options.common.flake8_tidy_imports,
|
||||
flake8_type_checking: options.common.flake8_type_checking,
|
||||
flake8_unused_arguments: options.common.flake8_unused_arguments,
|
||||
isort: options.common.isort,
|
||||
mccabe: options.common.mccabe,
|
||||
pep8_naming: options.common.pep8_naming,
|
||||
pycodestyle: options.common.pycodestyle,
|
||||
pydocstyle: options.common.pydocstyle,
|
||||
pyflakes: options.common.pyflakes,
|
||||
pylint: options.common.pylint,
|
||||
pyupgrade: options.common.pyupgrade,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -861,6 +886,7 @@ impl LintConfiguration {
|
|||
#[must_use]
|
||||
pub fn combine(self, config: Self) -> Self {
|
||||
Self {
|
||||
exclude: self.exclude.or(config.exclude),
|
||||
rule_selections: config
|
||||
.rule_selections
|
||||
.into_iter()
|
||||
|
@ -935,21 +961,28 @@ impl LintConfiguration {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FormatConfiguration {
|
||||
pub exclude: Option<Vec<FilePattern>>,
|
||||
pub preview: Option<PreviewMode>,
|
||||
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
|
||||
pub quote_style: Option<QuoteStyle>,
|
||||
|
||||
pub magic_trailing_comma: Option<MagicTrailingComma>,
|
||||
|
||||
pub line_ending: Option<LineEnding>,
|
||||
}
|
||||
|
||||
impl FormatConfiguration {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: FormatOptions) -> Result<Self> {
|
||||
pub fn from_options(options: FormatOptions, 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),
|
||||
indent_style: options.indent_style,
|
||||
quote_style: options.quote_style,
|
||||
|
@ -968,6 +1001,7 @@ impl FormatConfiguration {
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
Self {
|
||||
exclude: self.exclude.or(other.exclude),
|
||||
preview: self.preview.or(other.preview),
|
||||
indent_style: self.indent_style.or(other.indent_style),
|
||||
quote_style: self.quote_style.or(other.quote_style),
|
||||
|
|
|
@ -153,7 +153,7 @@ pub struct Options {
|
|||
pub preview: Option<bool>,
|
||||
|
||||
// File resolver options
|
||||
/// A list of file patterns to exclude from linting.
|
||||
/// A list of file patterns to exclude from formatting and linting.
|
||||
///
|
||||
/// Exclusions are based on globs, and can be either:
|
||||
///
|
||||
|
@ -178,7 +178,7 @@ pub struct Options {
|
|||
)]
|
||||
pub exclude: Option<Vec<String>>,
|
||||
|
||||
/// A list of file patterns to omit from linting, in addition to those
|
||||
/// A list of file patterns to omit from formatting and linting, in addition to those
|
||||
/// specified by `exclude`.
|
||||
///
|
||||
/// Exclusions are based on globs, and can be either:
|
||||
|
@ -377,13 +377,46 @@ pub struct Options {
|
|||
|
||||
/// The lint sections specified at the top level.
|
||||
#[serde(flatten)]
|
||||
pub lint_top_level: LintOptions,
|
||||
pub lint_top_level: LintCommonOptions,
|
||||
|
||||
/// Options to configure code formatting.
|
||||
#[option_group]
|
||||
pub format: Option<FormatOptions>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct LintOptions {
|
||||
#[serde(flatten)]
|
||||
pub common: LintCommonOptions,
|
||||
|
||||
/// A list of file patterns to exclude from linting 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>>,
|
||||
}
|
||||
|
||||
// Note: This struct should be inlined into [`LintOptions`] once support for the top-level lint settings
|
||||
// is removed.
|
||||
|
||||
/// Experimental section to configure Ruff's linting. This new section will eventually
|
||||
/// replace the top-level linting options.
|
||||
///
|
||||
|
@ -393,7 +426,7 @@ pub struct Options {
|
|||
Debug, PartialEq, Eq, Default, OptionsMetadata, CombineOptions, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct LintOptions {
|
||||
pub struct LintCommonOptions {
|
||||
/// A list of allowed "confusable" Unicode characters to ignore when
|
||||
/// enforcing `RUF001`, `RUF002`, and `RUF003`.
|
||||
#[option(
|
||||
|
@ -2469,6 +2502,31 @@ impl PyUpgradeOptions {
|
|||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct FormatOptions {
|
||||
/// A list of file patterns to exclude from formatting 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).
|
||||
///
|
||||
/// Note that you'll typically want to use
|
||||
/// [`extend-exclude`](#extend-exclude) to modify the excluded paths.
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
exclude = ["generated"]
|
||||
"#
|
||||
)]
|
||||
pub exclude: Option<Vec<String>>,
|
||||
|
||||
/// Whether to enable the unstable preview style formatting.
|
||||
#[option(
|
||||
default = "false",
|
||||
|
|
|
@ -161,7 +161,7 @@ mod tests {
|
|||
use ruff_linter::line_width::LineLength;
|
||||
use ruff_linter::settings::types::PatternPrefixPair;
|
||||
|
||||
use crate::options::{LintOptions, Options};
|
||||
use crate::options::{LintCommonOptions, Options};
|
||||
use crate::pyproject::{find_settings_toml, parse_pyproject_toml, Pyproject, Tools};
|
||||
use crate::tests::test_resource_path;
|
||||
|
||||
|
@ -236,9 +236,9 @@ select = ["E501"]
|
|||
pyproject.tool,
|
||||
Some(Tools {
|
||||
ruff: Some(Options {
|
||||
lint_top_level: LintOptions {
|
||||
lint_top_level: LintCommonOptions {
|
||||
select: Some(vec![codes::Pycodestyle::E501.into()]),
|
||||
..LintOptions::default()
|
||||
..LintCommonOptions::default()
|
||||
},
|
||||
..Options::default()
|
||||
})
|
||||
|
@ -257,10 +257,10 @@ ignore = ["E501"]
|
|||
pyproject.tool,
|
||||
Some(Tools {
|
||||
ruff: Some(Options {
|
||||
lint_top_level: LintOptions {
|
||||
lint_top_level: LintCommonOptions {
|
||||
extend_select: Some(vec![codes::Ruff::_100.into()]),
|
||||
ignore: Some(vec![codes::Pycodestyle::E501.into()]),
|
||||
..LintOptions::default()
|
||||
..LintCommonOptions::default()
|
||||
},
|
||||
..Options::default()
|
||||
})
|
||||
|
@ -315,12 +315,12 @@ other-attribute = 1
|
|||
"with_excluded_file/other_excluded_file.py".to_string(),
|
||||
]),
|
||||
|
||||
lint_top_level: LintOptions {
|
||||
lint_top_level: LintCommonOptions {
|
||||
per_file_ignores: Some(FxHashMap::from_iter([(
|
||||
"__init__.py".to_string(),
|
||||
vec![codes::Pyflakes::_401.into()]
|
||||
)])),
|
||||
..LintOptions::default()
|
||||
..LintCommonOptions::default()
|
||||
},
|
||||
..Options::default()
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
//! Discover Python files, and their corresponding [`Settings`], from the
|
||||
//! filesystem.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::RwLock;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, bail};
|
||||
use ignore::{DirEntry, WalkBuilder, WalkState};
|
||||
use ignore::{WalkBuilder, WalkState};
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use path_absolutize::path_dedot;
|
||||
|
@ -276,7 +278,7 @@ pub fn python_files_in_path(
|
|||
paths: &[PathBuf],
|
||||
pyproject_config: &PyprojectConfig,
|
||||
transformer: &dyn ConfigurationTransformer,
|
||||
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
|
||||
) -> Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver)> {
|
||||
// Normalize every path (e.g., convert from relative to absolute).
|
||||
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).unique().collect();
|
||||
|
||||
|
@ -305,13 +307,12 @@ pub fn python_files_in_path(
|
|||
}
|
||||
}
|
||||
|
||||
let (first_path, rest_paths) = paths
|
||||
.split_first()
|
||||
.ok_or_else(|| anyhow!("Expected at least one path to search for Python files"))?;
|
||||
// Create the `WalkBuilder`.
|
||||
let mut builder = WalkBuilder::new(
|
||||
paths
|
||||
.get(0)
|
||||
.ok_or_else(|| anyhow!("Expected at least one path to search for Python files"))?,
|
||||
);
|
||||
for path in &paths[1..] {
|
||||
let mut builder = WalkBuilder::new(first_path);
|
||||
for path in rest_paths {
|
||||
builder.add(path);
|
||||
}
|
||||
builder.standard_filters(pyproject_config.settings.file_resolver.respect_gitignore);
|
||||
|
@ -321,7 +322,7 @@ pub fn python_files_in_path(
|
|||
// Run the `WalkParallel` to collect all Python files.
|
||||
let error: std::sync::Mutex<Result<()>> = std::sync::Mutex::new(Ok(()));
|
||||
let resolver: RwLock<Resolver> = RwLock::new(resolver);
|
||||
let files: std::sync::Mutex<Vec<Result<DirEntry, ignore::Error>>> =
|
||||
let files: std::sync::Mutex<Vec<Result<ResolvedFile, ignore::Error>>> =
|
||||
std::sync::Mutex::new(vec![]);
|
||||
walker.run(|| {
|
||||
Box::new(|result| {
|
||||
|
@ -332,18 +333,14 @@ pub fn python_files_in_path(
|
|||
let resolver = resolver.read().unwrap();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if !settings.file_resolver.exclude.is_empty()
|
||||
&& match_exclusion(path, file_name, &settings.file_resolver.exclude)
|
||||
{
|
||||
if match_exclusion(path, file_name, &settings.file_resolver.exclude) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return WalkState::Skip;
|
||||
} else if !settings.file_resolver.extend_exclude.is_empty()
|
||||
&& match_exclusion(
|
||||
path,
|
||||
file_name,
|
||||
&settings.file_resolver.extend_exclude,
|
||||
)
|
||||
{
|
||||
} else if match_exclusion(
|
||||
path,
|
||||
file_name,
|
||||
&settings.file_resolver.extend_exclude,
|
||||
) {
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
return WalkState::Skip;
|
||||
}
|
||||
|
@ -386,30 +383,37 @@ pub fn python_files_in_path(
|
|||
}
|
||||
}
|
||||
|
||||
if result.as_ref().map_or(true, |entry| {
|
||||
// Ignore directories
|
||||
if entry.file_type().map_or(true, |ft| ft.is_dir()) {
|
||||
false
|
||||
} else if entry.depth() == 0 {
|
||||
// Accept all files that are passed-in directly.
|
||||
true
|
||||
} else {
|
||||
// Otherwise, check if the file is included.
|
||||
let path = entry.path();
|
||||
let resolver = resolver.read().unwrap();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.file_resolver.include.is_match(path) {
|
||||
debug!("Included path via `include`: {:?}", path);
|
||||
true
|
||||
} else if settings.file_resolver.extend_include.is_match(path) {
|
||||
debug!("Included path via `extend-include`: {:?}", path);
|
||||
true
|
||||
match result {
|
||||
Ok(entry) => {
|
||||
// Ignore directories
|
||||
let resolved = if entry.file_type().map_or(true, |ft| ft.is_dir()) {
|
||||
None
|
||||
} else if entry.depth() == 0 {
|
||||
// Accept all files that are passed-in directly.
|
||||
Some(ResolvedFile::Root(entry.into_path()))
|
||||
} else {
|
||||
false
|
||||
// Otherwise, check if the file is included.
|
||||
let path = entry.path();
|
||||
let resolver = resolver.read().unwrap();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if settings.file_resolver.include.is_match(path) {
|
||||
debug!("Included path via `include`: {:?}", path);
|
||||
Some(ResolvedFile::Nested(entry.into_path()))
|
||||
} else if settings.file_resolver.extend_include.is_match(path) {
|
||||
debug!("Included path via `extend-include`: {:?}", path);
|
||||
Some(ResolvedFile::Nested(entry.into_path()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(resolved) = resolved {
|
||||
files.lock().unwrap().push(Ok(resolved));
|
||||
}
|
||||
}
|
||||
}) {
|
||||
files.lock().unwrap().push(result);
|
||||
Err(err) => {
|
||||
files.lock().unwrap().push(Err(err));
|
||||
}
|
||||
}
|
||||
|
||||
WalkState::Continue
|
||||
|
@ -421,6 +425,51 @@ pub fn python_files_in_path(
|
|||
Ok((files.into_inner().unwrap(), resolver.into_inner().unwrap()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ResolvedFile {
|
||||
/// File explicitly passed to the CLI
|
||||
Root(PathBuf),
|
||||
/// File in a sub-directory
|
||||
Nested(PathBuf),
|
||||
}
|
||||
|
||||
impl ResolvedFile {
|
||||
pub fn into_path(self) -> PathBuf {
|
||||
match self {
|
||||
ResolvedFile::Root(path) => path,
|
||||
ResolvedFile::Nested(path) => path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
match self {
|
||||
ResolvedFile::Root(root) => root.as_path(),
|
||||
ResolvedFile::Nested(root) => root.as_path(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_name(&self) -> &OsStr {
|
||||
let path = self.path();
|
||||
path.file_name().unwrap_or(path.as_os_str())
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
matches!(self, ResolvedFile::Root(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ResolvedFile {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ResolvedFile {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.path().cmp(other.path())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
|
||||
pub fn python_file_at_path(
|
||||
path: &Path,
|
||||
|
@ -458,25 +507,17 @@ fn is_file_excluded(
|
|||
) -> bool {
|
||||
// TODO(charlie): Respect gitignore.
|
||||
for path in path.ancestors() {
|
||||
if path.file_name().is_none() {
|
||||
break;
|
||||
}
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if !settings.file_resolver.exclude.is_empty()
|
||||
&& match_exclusion(path, file_name, &settings.file_resolver.exclude)
|
||||
{
|
||||
if match_exclusion(path, file_name, &settings.file_resolver.exclude) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return true;
|
||||
} else if !settings.file_resolver.extend_exclude.is_empty()
|
||||
&& match_exclusion(path, file_name, &settings.file_resolver.extend_exclude)
|
||||
{
|
||||
} else if match_exclusion(path, file_name, &settings.file_resolver.extend_exclude) {
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
debug!("Ignored path due to error in parsing: {:?}", path);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
if path == settings.file_resolver.project_root {
|
||||
// Bail out; we'd end up past the project root on the next iteration
|
||||
|
@ -489,7 +530,7 @@ fn is_file_excluded(
|
|||
|
||||
/// Return `true` if the given file should be ignored based on the exclusion
|
||||
/// criteria.
|
||||
fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
|
||||
pub fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
|
||||
file_path: P,
|
||||
file_basename: R,
|
||||
exclusion: &globset::GlobSet,
|
||||
|
@ -515,7 +556,7 @@ mod tests {
|
|||
use crate::resolver::{
|
||||
is_file_excluded, match_exclusion, python_files_in_path, resolve_root_settings,
|
||||
ConfigurationTransformer, PyprojectConfig, PyprojectDiscoveryStrategy, Relativity,
|
||||
Resolver,
|
||||
ResolvedFile, Resolver,
|
||||
};
|
||||
use crate::settings::Settings;
|
||||
use crate::tests::test_resource_path;
|
||||
|
@ -584,12 +625,12 @@ mod tests {
|
|||
&NoOpTransformer,
|
||||
)?;
|
||||
let paths = paths
|
||||
.iter()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(ignore::DirEntry::path)
|
||||
.map(ResolvedFile::into_path)
|
||||
.sorted()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(paths, &[file2, file1]);
|
||||
assert_eq!(paths, [file2, file1]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ impl FileResolverSettings {
|
|||
|
||||
#[derive(CacheKey, Clone, Debug)]
|
||||
pub struct FormatterSettings {
|
||||
pub exclude: FilePatternSet,
|
||||
pub preview: PreviewMode,
|
||||
|
||||
pub line_width: LineWidth,
|
||||
|
@ -162,6 +163,7 @@ impl Default for FormatterSettings {
|
|||
let default_options = PyFormatOptions::default();
|
||||
|
||||
Self {
|
||||
exclude: FilePatternSet::default(),
|
||||
preview: ruff_python_formatter::PreviewMode::Disabled,
|
||||
line_width: default_options.line_width(),
|
||||
line_ending: LineEnding::Lf,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue