mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-02 18:02:23 +00:00
Move pyproject_config
into Resolver
(#9453)
## Summary Sort of a random PR to make the coupling between `pyproject_config` and `resolver` more explicit by passing it to the `Resolver`, rather than threading it through to each individual method.
This commit is contained in:
parent
79f4abbb8d
commit
4a3bb67b5f
9 changed files with 144 additions and 149 deletions
|
@ -25,10 +25,9 @@ use ruff_notebook::NotebookIndex;
|
||||||
use ruff_python_ast::imports::ImportMap;
|
use ruff_python_ast::imports::ImportMap;
|
||||||
use ruff_source_file::SourceFileBuilder;
|
use ruff_source_file::SourceFileBuilder;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy, Resolver};
|
use ruff_workspace::resolver::Resolver;
|
||||||
use ruff_workspace::Settings;
|
use ruff_workspace::Settings;
|
||||||
|
|
||||||
use crate::cache;
|
|
||||||
use crate::diagnostics::Diagnostics;
|
use crate::diagnostics::Diagnostics;
|
||||||
|
|
||||||
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
/// [`Path`] that is relative to the package root in [`PackageCache`].
|
||||||
|
@ -443,7 +442,7 @@ pub(super) struct CacheMessage {
|
||||||
pub(crate) trait PackageCaches {
|
pub(crate) trait PackageCaches {
|
||||||
fn get(&self, package_root: &Path) -> Option<&Cache>;
|
fn get(&self, package_root: &Path) -> Option<&Cache>;
|
||||||
|
|
||||||
fn persist(self) -> anyhow::Result<()>;
|
fn persist(self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PackageCaches for Option<T>
|
impl<T> PackageCaches for Option<T>
|
||||||
|
@ -469,28 +468,18 @@ pub(crate) struct PackageCacheMap<'a>(FxHashMap<&'a Path, Cache>);
|
||||||
|
|
||||||
impl<'a> PackageCacheMap<'a> {
|
impl<'a> PackageCacheMap<'a> {
|
||||||
pub(crate) fn init(
|
pub(crate) fn init(
|
||||||
pyproject_config: &PyprojectConfig,
|
|
||||||
package_roots: &FxHashMap<&'a Path, Option<&'a Path>>,
|
package_roots: &FxHashMap<&'a Path, Option<&'a Path>>,
|
||||||
resolver: &Resolver,
|
resolver: &Resolver,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
fn init_cache(path: &Path) {
|
fn init_cache(path: &Path) {
|
||||||
if let Err(e) = cache::init(path) {
|
if let Err(e) = init(path) {
|
||||||
error!("Failed to initialize cache at {}: {e:?}", path.display());
|
error!("Failed to initialize cache at {}: {e:?}", path.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match pyproject_config.strategy {
|
for settings in resolver.settings() {
|
||||||
PyprojectDiscoveryStrategy::Fixed => {
|
|
||||||
init_cache(&pyproject_config.settings.cache_dir);
|
|
||||||
}
|
|
||||||
PyprojectDiscoveryStrategy::Hierarchical => {
|
|
||||||
for settings in
|
|
||||||
std::iter::once(&pyproject_config.settings).chain(resolver.settings())
|
|
||||||
{
|
|
||||||
init_cache(&settings.cache_dir);
|
init_cache(&settings.cache_dir);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self(
|
Self(
|
||||||
package_roots
|
package_roots
|
||||||
|
@ -499,7 +488,7 @@ impl<'a> PackageCacheMap<'a> {
|
||||||
.unique()
|
.unique()
|
||||||
.par_bridge()
|
.par_bridge()
|
||||||
.map(|cache_root| {
|
.map(|cache_root| {
|
||||||
let settings = resolver.resolve(cache_root, pyproject_config);
|
let settings = resolver.resolve(cache_root);
|
||||||
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
let cache = Cache::open(cache_root.to_path_buf(), settings);
|
||||||
(cache_root, cache)
|
(cache_root, cache)
|
||||||
})
|
})
|
||||||
|
|
|
@ -38,7 +38,6 @@ pub(crate) fn add_noqa(
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(ResolvedFile::path)
|
.map(ResolvedFile::path)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
pyproject_config,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
@ -57,7 +56,7 @@ pub(crate) fn add_noqa(
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|parent| package_roots.get(parent))
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.and_then(|package| *package);
|
.and_then(|package| *package);
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
let source_kind = match SourceKind::from_path(path, source_type) {
|
let source_kind = match SourceKind::from_path(path, source_type) {
|
||||||
Ok(Some(source_kind)) => source_kind,
|
Ok(Some(source_kind)) => source_kind,
|
||||||
Ok(None) => return None,
|
Ok(None) => return None,
|
||||||
|
|
|
@ -57,16 +57,11 @@ pub(crate) fn check(
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(ResolvedFile::path)
|
.map(ResolvedFile::path)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
pyproject_config,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load the caches.
|
// Load the caches.
|
||||||
let caches = if bool::from(cache) {
|
let caches = if bool::from(cache) {
|
||||||
Some(PackageCacheMap::init(
|
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||||
pyproject_config,
|
|
||||||
&package_roots,
|
|
||||||
&resolver,
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -81,7 +76,7 @@ pub(crate) fn check(
|
||||||
.and_then(|parent| package_roots.get(parent))
|
.and_then(|parent| package_roots.get(parent))
|
||||||
.and_then(|package| *package);
|
.and_then(|package| *package);
|
||||||
|
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
|
|
||||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
&& match_exclusion(
|
&& match_exclusion(
|
||||||
|
@ -128,7 +123,7 @@ pub(crate) fn check(
|
||||||
|
|
||||||
Some(result.unwrap_or_else(|(path, message)| {
|
Some(result.unwrap_or_else(|(path, message)| {
|
||||||
if let Some(path) = &path {
|
if let Some(path) = &path {
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
if settings.linter.rules.enabled(Rule::IOError) {
|
if settings.linter.rules.enabled(Rule::IOError) {
|
||||||
let dummy =
|
let dummy =
|
||||||
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
|
||||||
|
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||||
|
|
||||||
use ruff_linter::packaging;
|
use ruff_linter::packaging;
|
||||||
use ruff_linter::settings::flags;
|
use ruff_linter::settings::flags;
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig};
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
|
||||||
|
|
||||||
use crate::args::CliOverrides;
|
use crate::args::CliOverrides;
|
||||||
use crate::diagnostics::{lint_stdin, Diagnostics};
|
use crate::diagnostics::{lint_stdin, Diagnostics};
|
||||||
|
@ -18,20 +18,20 @@ pub(crate) fn check_stdin(
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
fix_mode: flags::FixMode,
|
fix_mode: flags::FixMode,
|
||||||
) -> Result<Diagnostics> {
|
) -> Result<Diagnostics> {
|
||||||
if pyproject_config.settings.file_resolver.force_exclude {
|
let mut resolver = Resolver::new(pyproject_config);
|
||||||
|
|
||||||
|
if resolver.force_exclude() {
|
||||||
if let Some(filename) = filename {
|
if let Some(filename) = filename {
|
||||||
if !python_file_at_path(filename, pyproject_config, overrides)? {
|
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||||
if fix_mode.is_apply() {
|
if fix_mode.is_apply() {
|
||||||
parrot_stdin()?;
|
parrot_stdin()?;
|
||||||
}
|
}
|
||||||
return Ok(Diagnostics::default());
|
return Ok(Diagnostics::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let lint_settings = &pyproject_config.settings.linter;
|
if filename.file_name().is_some_and(|name| {
|
||||||
if filename
|
match_exclusion(filename, name, &resolver.base_settings().linter.exclude)
|
||||||
.file_name()
|
}) {
|
||||||
.is_some_and(|name| match_exclusion(filename, name, &lint_settings.exclude))
|
|
||||||
{
|
|
||||||
if fix_mode.is_apply() {
|
if fix_mode.is_apply() {
|
||||||
parrot_stdin()?;
|
parrot_stdin()?;
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ pub(crate) fn check_stdin(
|
||||||
}
|
}
|
||||||
let stdin = read_from_stdin()?;
|
let stdin = read_from_stdin()?;
|
||||||
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
let package_root = filename.and_then(Path::parent).and_then(|path| {
|
||||||
packaging::detect_package_root(path, &pyproject_config.settings.linter.namespace_packages)
|
packaging::detect_package_root(path, &resolver.base_settings().linter.namespace_packages)
|
||||||
});
|
});
|
||||||
let mut diagnostics = lint_stdin(
|
let mut diagnostics = lint_stdin(
|
||||||
filename,
|
filename,
|
||||||
package_root,
|
package_root,
|
||||||
stdin,
|
stdin,
|
||||||
&pyproject_config.settings,
|
resolver.base_settings(),
|
||||||
noqa,
|
noqa,
|
||||||
fix_mode,
|
fix_mode,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -25,9 +25,7 @@ use ruff_linter::warn_user_once;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use ruff_workspace::resolver::{
|
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
|
||||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
|
||||||
};
|
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{CliOverrides, FormatArguments};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
|
@ -79,7 +77,7 @@ pub(crate) fn format(
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn_incompatible_formatter_settings(&pyproject_config, Some(&resolver));
|
warn_incompatible_formatter_settings(&resolver);
|
||||||
|
|
||||||
// Discover the package root for each Python file.
|
// Discover the package root for each Python file.
|
||||||
let package_roots = resolver.package_roots(
|
let package_roots = resolver.package_roots(
|
||||||
|
@ -88,7 +86,6 @@ pub(crate) fn format(
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(ResolvedFile::path)
|
.map(ResolvedFile::path)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
&pyproject_config,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let caches = if cli.no_cache {
|
let caches = if cli.no_cache {
|
||||||
|
@ -99,11 +96,7 @@ pub(crate) fn format(
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
crate::warn_user!("Detected debug build without --no-cache.");
|
crate::warn_user!("Detected debug build without --no-cache.");
|
||||||
|
|
||||||
Some(PackageCacheMap::init(
|
Some(PackageCacheMap::init(&package_roots, &resolver))
|
||||||
&pyproject_config,
|
|
||||||
&package_roots,
|
|
||||||
&resolver,
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
@ -118,7 +111,7 @@ pub(crate) fn format(
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = resolver.resolve(path, &pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
|
|
||||||
// Ignore files that are excluded from formatting
|
// Ignore files that are excluded from formatting
|
||||||
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
|
@ -723,15 +716,10 @@ impl Display for FormatCommandError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn warn_incompatible_formatter_settings(
|
pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||||
pyproject_config: &PyprojectConfig,
|
|
||||||
resolver: Option<&Resolver>,
|
|
||||||
) {
|
|
||||||
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
// First, collect all rules that are incompatible regardless of the linter-specific settings.
|
||||||
let mut incompatible_rules = FxHashSet::default();
|
let mut incompatible_rules = FxHashSet::default();
|
||||||
for setting in std::iter::once(&pyproject_config.settings)
|
for setting in resolver.settings() {
|
||||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
|
||||||
{
|
|
||||||
for rule in [
|
for rule in [
|
||||||
// The formatter might collapse implicit string concatenation on a single line.
|
// The formatter might collapse implicit string concatenation on a single line.
|
||||||
Rule::SingleLineImplicitStringConcatenation,
|
Rule::SingleLineImplicitStringConcatenation,
|
||||||
|
@ -760,9 +748,7 @@ pub(super) fn warn_incompatible_formatter_settings(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, validate settings-specific incompatibilities.
|
// Next, validate settings-specific incompatibilities.
|
||||||
for setting in std::iter::once(&pyproject_config.settings)
|
for setting in resolver.settings() {
|
||||||
.chain(resolver.iter().flat_map(|resolver| resolver.settings()))
|
|
||||||
{
|
|
||||||
// Validate all rules that rely on tab styles.
|
// Validate all rules that rely on tab styles.
|
||||||
if setting.linter.rules.enabled(Rule::TabIndentation)
|
if setting.linter.rules.enabled(Rule::TabIndentation)
|
||||||
&& setting.formatter.indent_style.is_tab()
|
&& setting.formatter.indent_style.is_tab()
|
||||||
|
|
|
@ -6,7 +6,7 @@ use log::error;
|
||||||
|
|
||||||
use ruff_linter::source_kind::SourceKind;
|
use ruff_linter::source_kind::SourceKind;
|
||||||
use ruff_python_ast::{PySourceType, SourceType};
|
use ruff_python_ast::{PySourceType, SourceType};
|
||||||
use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
|
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
|
||||||
use ruff_workspace::FormatterSettings;
|
use ruff_workspace::FormatterSettings;
|
||||||
|
|
||||||
use crate::args::{CliOverrides, FormatArguments};
|
use crate::args::{CliOverrides, FormatArguments};
|
||||||
|
@ -27,24 +27,23 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
warn_incompatible_formatter_settings(&pyproject_config, None);
|
let mut resolver = Resolver::new(&pyproject_config);
|
||||||
|
warn_incompatible_formatter_settings(&resolver);
|
||||||
|
|
||||||
let mode = FormatMode::from_cli(cli);
|
let mode = FormatMode::from_cli(cli);
|
||||||
|
|
||||||
if pyproject_config.settings.file_resolver.force_exclude {
|
if resolver.force_exclude() {
|
||||||
if let Some(filename) = cli.stdin_filename.as_deref() {
|
if let Some(filename) = cli.stdin_filename.as_deref() {
|
||||||
if !python_file_at_path(filename, &pyproject_config, overrides)? {
|
if !python_file_at_path(filename, &mut resolver, overrides)? {
|
||||||
if mode.is_write() {
|
if mode.is_write() {
|
||||||
parrot_stdin()?;
|
parrot_stdin()?;
|
||||||
}
|
}
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
let format_settings = &pyproject_config.settings.formatter;
|
if filename.file_name().is_some_and(|name| {
|
||||||
if filename
|
match_exclusion(filename, name, &resolver.base_settings().formatter.exclude)
|
||||||
.file_name()
|
}) {
|
||||||
.is_some_and(|name| match_exclusion(filename, name, &format_settings.exclude))
|
|
||||||
{
|
|
||||||
if mode.is_write() {
|
if mode.is_write() {
|
||||||
parrot_stdin()?;
|
parrot_stdin()?;
|
||||||
}
|
}
|
||||||
|
@ -63,12 +62,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format the file.
|
// Format the file.
|
||||||
match format_source_code(
|
match format_source_code(path, &resolver.base_settings().formatter, source_type, mode) {
|
||||||
path,
|
|
||||||
&pyproject_config.settings.formatter,
|
|
||||||
source_type,
|
|
||||||
mode,
|
|
||||||
) {
|
|
||||||
Ok(result) => match mode {
|
Ok(result) => match mode {
|
||||||
FormatMode::Write => Ok(ExitStatus::Success),
|
FormatMode::Write => Ok(ExitStatus::Success),
|
||||||
FormatMode::Check | FormatMode::Diff => {
|
FormatMode::Check | FormatMode::Diff => {
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub(crate) fn show_settings(
|
||||||
bail!("No files found under the given path");
|
bail!("No files found under the given path");
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = resolver.resolve(&path, pyproject_config);
|
let settings = resolver.resolve(&path);
|
||||||
|
|
||||||
writeln!(writer, "Resolved settings for: {path:?}")?;
|
writeln!(writer, "Resolved settings for: {path:?}")?;
|
||||||
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
if let Some(settings_path) = pyproject_config.path.as_ref() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use ruff_cli::args::{FormatCommand, LogLevelArgs};
|
use ruff_cli::args::{CliOverrides, FormatArguments, FormatCommand, LogLevelArgs};
|
||||||
use ruff_cli::resolve::resolve;
|
use ruff_cli::resolve::resolve;
|
||||||
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||||
use ruff_linter::logging::LogLevel;
|
use ruff_linter::logging::LogLevel;
|
||||||
|
@ -38,24 +38,24 @@ use ruff_python_formatter::{
|
||||||
use ruff_python_parser::ParseError;
|
use ruff_python_parser::ParseError;
|
||||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||||
|
|
||||||
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, CliOverrides)> {
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn ruff_check_paths(
|
|
||||||
dirs: &[PathBuf],
|
|
||||||
) -> anyhow::Result<(
|
|
||||||
Vec<Result<ResolvedFile, ignore::Error>>,
|
|
||||||
Resolver,
|
|
||||||
PyprojectConfig,
|
|
||||||
)> {
|
|
||||||
let args_matches = FormatCommand::command()
|
let args_matches = FormatCommand::command()
|
||||||
.no_binary_name(true)
|
.no_binary_name(true)
|
||||||
.get_matches_from(dirs);
|
.get_matches_from(dirs);
|
||||||
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
|
||||||
let (cli, overrides) = arguments.partition();
|
let (cli, overrides) = arguments.partition();
|
||||||
|
Ok((cli, overrides))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the [`PyprojectConfig`] to use for formatting.
|
||||||
|
fn find_pyproject_config(
|
||||||
|
cli: &FormatArguments,
|
||||||
|
overrides: &CliOverrides,
|
||||||
|
) -> anyhow::Result<PyprojectConfig> {
|
||||||
let mut pyproject_config = resolve(
|
let mut pyproject_config = resolve(
|
||||||
cli.isolated,
|
cli.isolated,
|
||||||
cli.config.as_deref(),
|
cli.config.as_deref(),
|
||||||
&overrides,
|
overrides,
|
||||||
cli.stdin_filename.as_deref(),
|
cli.stdin_filename.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
// We don't want to format pyproject.toml
|
// We don't want to format pyproject.toml
|
||||||
|
@ -64,11 +64,18 @@ fn ruff_check_paths(
|
||||||
FilePattern::Builtin("*.pyi"),
|
FilePattern::Builtin("*.pyi"),
|
||||||
])
|
])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, &overrides)?;
|
Ok(pyproject_config)
|
||||||
if paths.is_empty() {
|
|
||||||
bail!("no python files in {:?}", dirs)
|
|
||||||
}
|
}
|
||||||
Ok((paths, resolver, pyproject_config))
|
|
||||||
|
/// Find files that ruff would check so we can format them. Adapted from `ruff_cli`.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn ruff_check_paths<'a>(
|
||||||
|
pyproject_config: &'a PyprojectConfig,
|
||||||
|
cli: &FormatArguments,
|
||||||
|
overrides: &CliOverrides,
|
||||||
|
) -> anyhow::Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||||
|
let (paths, resolver) = python_files_in_path(&cli.files, pyproject_config, overrides)?;
|
||||||
|
Ok((paths, resolver))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collects statistics over the formatted files to compute the Jaccard index or the similarity
|
/// Collects statistics over the formatted files to compute the Jaccard index or the similarity
|
||||||
|
@ -452,11 +459,17 @@ fn format_dev_project(
|
||||||
files[0].display()
|
files[0].display()
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO(konstin): black excludes
|
// TODO(konstin): Respect black's excludes.
|
||||||
|
|
||||||
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
// Find files to check (or in this case, format twice). Adapted from ruff_cli
|
||||||
// First argument is ignored
|
// First argument is ignored
|
||||||
let (paths, resolver, pyproject_config) = ruff_check_paths(files)?;
|
let (cli, overrides) = parse_cli(files)?;
|
||||||
|
let pyproject_config = find_pyproject_config(&cli, &overrides)?;
|
||||||
|
let (paths, resolver) = ruff_check_paths(&pyproject_config, &cli, &overrides)?;
|
||||||
|
|
||||||
|
if paths.is_empty() {
|
||||||
|
bail!("No Python files found under the given path(s)");
|
||||||
|
}
|
||||||
|
|
||||||
let results = {
|
let results = {
|
||||||
let pb_span =
|
let pb_span =
|
||||||
|
@ -469,14 +482,7 @@ fn format_dev_project(
|
||||||
#[cfg(feature = "singlethreaded")]
|
#[cfg(feature = "singlethreaded")]
|
||||||
let iter = { paths.into_iter() };
|
let iter = { paths.into_iter() };
|
||||||
iter.map(|path| {
|
iter.map(|path| {
|
||||||
let result = format_dir_entry(
|
let result = format_dir_entry(path, stability_check, write, &black_options, &resolver);
|
||||||
path,
|
|
||||||
stability_check,
|
|
||||||
write,
|
|
||||||
&black_options,
|
|
||||||
&resolver,
|
|
||||||
&pyproject_config,
|
|
||||||
);
|
|
||||||
pb_span.pb_inc(1);
|
pb_span.pb_inc(1);
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
|
@ -526,14 +532,13 @@ fn format_dev_project(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error handling in between walkdir and `format_dev_file`
|
/// Error handling in between walkdir and `format_dev_file`.
|
||||||
fn format_dir_entry(
|
fn format_dir_entry(
|
||||||
resolved_file: Result<ResolvedFile, ignore::Error>,
|
resolved_file: Result<ResolvedFile, ignore::Error>,
|
||||||
stability_check: bool,
|
stability_check: bool,
|
||||||
write: bool,
|
write: bool,
|
||||||
options: &BlackOptions,
|
options: &BlackOptions,
|
||||||
resolver: &Resolver,
|
resolver: &Resolver,
|
||||||
pyproject_config: &PyprojectConfig,
|
|
||||||
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
) -> anyhow::Result<(Result<Statistics, CheckFileError>, PathBuf), Error> {
|
||||||
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
let resolved_file = resolved_file.context("Iterating the files in the repository failed")?;
|
||||||
// For some reason it does not filter in the beginning
|
// For some reason it does not filter in the beginning
|
||||||
|
@ -544,7 +549,7 @@ fn format_dir_entry(
|
||||||
let path = resolved_file.into_path();
|
let path = resolved_file.into_path();
|
||||||
let mut options = options.to_py_format_options(&path);
|
let mut options = options.to_py_format_options(&path);
|
||||||
|
|
||||||
let settings = resolver.resolve(&path, pyproject_config);
|
let settings = resolver.resolve(&path);
|
||||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||||
if settings.formatter.line_width != LineWidth::default() {
|
if settings.formatter.line_width != LineWidth::default() {
|
||||||
options = options.with_line_width(settings.formatter.line_width);
|
options = options.with_line_width(settings.formatter.line_width);
|
||||||
|
|
|
@ -11,7 +11,7 @@ use anyhow::Result;
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use globset::{Candidate, GlobSet};
|
use globset::{Candidate, GlobSet};
|
||||||
use ignore::{WalkBuilder, WalkState};
|
use ignore::{WalkBuilder, WalkState};
|
||||||
use itertools::Itertools;
|
use itertools::{Either, Itertools};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use path_absolutize::path_dedot;
|
use path_absolutize::path_dedot;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
@ -25,6 +25,7 @@ use crate::pyproject::settings_toml;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
|
|
||||||
/// The configuration information from a `pyproject.toml` file.
|
/// The configuration information from a `pyproject.toml` file.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PyprojectConfig {
|
pub struct PyprojectConfig {
|
||||||
/// The strategy used to discover the relevant `pyproject.toml` file for
|
/// The strategy used to discover the relevant `pyproject.toml` file for
|
||||||
/// each Python file.
|
/// each Python file.
|
||||||
|
@ -63,10 +64,12 @@ pub enum PyprojectDiscoveryStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyprojectDiscoveryStrategy {
|
impl PyprojectDiscoveryStrategy {
|
||||||
|
#[inline]
|
||||||
pub const fn is_fixed(self) -> bool {
|
pub const fn is_fixed(self) -> bool {
|
||||||
matches!(self, PyprojectDiscoveryStrategy::Fixed)
|
matches!(self, PyprojectDiscoveryStrategy::Fixed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub const fn is_hierarchical(self) -> bool {
|
pub const fn is_hierarchical(self) -> bool {
|
||||||
matches!(self, PyprojectDiscoveryStrategy::Hierarchical)
|
matches!(self, PyprojectDiscoveryStrategy::Hierarchical)
|
||||||
}
|
}
|
||||||
|
@ -94,40 +97,68 @@ impl Relativity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug)]
|
||||||
pub struct Resolver {
|
pub struct Resolver<'a> {
|
||||||
|
pyproject_config: &'a PyprojectConfig,
|
||||||
settings: BTreeMap<PathBuf, Settings>,
|
settings: BTreeMap<PathBuf, Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolver {
|
impl<'a> Resolver<'a> {
|
||||||
|
/// Create a new [`Resolver`] for the given [`PyprojectConfig`].
|
||||||
|
pub fn new(pyproject_config: &'a PyprojectConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
pyproject_config,
|
||||||
|
settings: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the [`Settings`] from the [`PyprojectConfig`].
|
||||||
|
#[inline]
|
||||||
|
pub fn base_settings(&self) -> &Settings {
|
||||||
|
&self.pyproject_config.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the [`Resolver`] is using a hierarchical discovery strategy.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_hierarchical(&self) -> bool {
|
||||||
|
self.pyproject_config.strategy.is_hierarchical()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the [`Resolver`] should force-exclude files passed directly to the CLI.
|
||||||
|
#[inline]
|
||||||
|
pub fn force_exclude(&self) -> bool {
|
||||||
|
self.pyproject_config.settings.file_resolver.force_exclude
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the [`Resolver`] should respect `.gitignore` files.
|
||||||
|
#[inline]
|
||||||
|
pub fn respect_gitignore(&self) -> bool {
|
||||||
|
self.pyproject_config
|
||||||
|
.settings
|
||||||
|
.file_resolver
|
||||||
|
.respect_gitignore
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a resolved [`Settings`] under a given [`PathBuf`] scope.
|
/// Add a resolved [`Settings`] under a given [`PathBuf`] scope.
|
||||||
fn add(&mut self, path: PathBuf, settings: Settings) {
|
fn add(&mut self, path: PathBuf, settings: Settings) {
|
||||||
self.settings.insert(path, settings);
|
self.settings.insert(path, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the appropriate [`Settings`] for a given [`Path`].
|
/// Return the appropriate [`Settings`] for a given [`Path`].
|
||||||
pub fn resolve<'a>(
|
pub fn resolve(&self, path: &Path) -> &Settings {
|
||||||
&'a self,
|
match self.pyproject_config.strategy {
|
||||||
path: &Path,
|
PyprojectDiscoveryStrategy::Fixed => &self.pyproject_config.settings,
|
||||||
pyproject_config: &'a PyprojectConfig,
|
|
||||||
) -> &'a Settings {
|
|
||||||
match pyproject_config.strategy {
|
|
||||||
PyprojectDiscoveryStrategy::Fixed => &pyproject_config.settings,
|
|
||||||
PyprojectDiscoveryStrategy::Hierarchical => self
|
PyprojectDiscoveryStrategy::Hierarchical => self
|
||||||
.settings
|
.settings
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find_map(|(root, settings)| path.starts_with(root).then_some(settings))
|
.find_map(|(root, settings)| path.starts_with(root).then_some(settings))
|
||||||
.unwrap_or(&pyproject_config.settings),
|
.unwrap_or(&self.pyproject_config.settings),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a mapping from Python package to its package root.
|
/// Return a mapping from Python package to its package root.
|
||||||
pub fn package_roots<'a>(
|
pub fn package_roots(&'a self, files: &[&'a Path]) -> FxHashMap<&'a Path, Option<&'a Path>> {
|
||||||
&'a self,
|
|
||||||
files: &[&'a Path],
|
|
||||||
pyproject_config: &'a PyprojectConfig,
|
|
||||||
) -> FxHashMap<&'a Path, Option<&'a Path>> {
|
|
||||||
// Pre-populate the module cache, since the list of files could (but isn't
|
// Pre-populate the module cache, since the list of files could (but isn't
|
||||||
// required to) contain some `__init__.py` files.
|
// required to) contain some `__init__.py` files.
|
||||||
let mut package_cache: FxHashMap<&Path, bool> = FxHashMap::default();
|
let mut package_cache: FxHashMap<&Path, bool> = FxHashMap::default();
|
||||||
|
@ -154,10 +185,7 @@ impl Resolver {
|
||||||
std::collections::hash_map::Entry::Occupied(_) => continue,
|
std::collections::hash_map::Entry::Occupied(_) => continue,
|
||||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||||
let namespace_packages = if has_namespace_packages {
|
let namespace_packages = if has_namespace_packages {
|
||||||
self.resolve(file, pyproject_config)
|
self.resolve(file).linter.namespace_packages.as_slice()
|
||||||
.linter
|
|
||||||
.namespace_packages
|
|
||||||
.as_slice()
|
|
||||||
} else {
|
} else {
|
||||||
&[]
|
&[]
|
||||||
};
|
};
|
||||||
|
@ -176,7 +204,12 @@ impl Resolver {
|
||||||
|
|
||||||
/// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
|
/// Return an iterator over the resolved [`Settings`] in this [`Resolver`].
|
||||||
pub fn settings(&self) -> impl Iterator<Item = &Settings> {
|
pub fn settings(&self) -> impl Iterator<Item = &Settings> {
|
||||||
self.settings.values()
|
match self.pyproject_config.strategy {
|
||||||
|
PyprojectDiscoveryStrategy::Fixed => {
|
||||||
|
Either::Left(std::iter::once(&self.pyproject_config.settings))
|
||||||
|
}
|
||||||
|
PyprojectDiscoveryStrategy::Hierarchical => Either::Right(self.settings.values()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,18 +321,18 @@ pub fn resolve_root_settings(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
|
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
|
||||||
pub fn python_files_in_path(
|
pub fn python_files_in_path<'a>(
|
||||||
paths: &[PathBuf],
|
paths: &[PathBuf],
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &'a PyprojectConfig,
|
||||||
transformer: &dyn ConfigurationTransformer,
|
transformer: &dyn ConfigurationTransformer,
|
||||||
) -> Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver)> {
|
) -> Result<(Vec<Result<ResolvedFile, ignore::Error>>, Resolver<'a>)> {
|
||||||
// Normalize every path (e.g., convert from relative to absolute).
|
// Normalize every path (e.g., convert from relative to absolute).
|
||||||
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).unique().collect();
|
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).unique().collect();
|
||||||
|
|
||||||
// Search for `pyproject.toml` files in all parent directories.
|
// Search for `pyproject.toml` files in all parent directories.
|
||||||
let mut resolver = Resolver::default();
|
let mut resolver = Resolver::new(pyproject_config);
|
||||||
let mut seen = FxHashSet::default();
|
let mut seen = FxHashSet::default();
|
||||||
if pyproject_config.strategy.is_hierarchical() {
|
if resolver.is_hierarchical() {
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
for ancestor in path.ancestors() {
|
for ancestor in path.ancestors() {
|
||||||
if seen.insert(ancestor) {
|
if seen.insert(ancestor) {
|
||||||
|
@ -315,8 +348,8 @@ pub fn python_files_in_path(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the paths themselves are excluded.
|
// Check if the paths themselves are excluded.
|
||||||
if pyproject_config.settings.file_resolver.force_exclude {
|
if resolver.force_exclude() {
|
||||||
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_config));
|
paths.retain(|path| !is_file_excluded(path, &resolver));
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Ok((vec![], resolver));
|
return Ok((vec![], resolver));
|
||||||
}
|
}
|
||||||
|
@ -330,11 +363,12 @@ pub fn python_files_in_path(
|
||||||
for path in rest_paths {
|
for path in rest_paths {
|
||||||
builder.add(path);
|
builder.add(path);
|
||||||
}
|
}
|
||||||
builder.standard_filters(pyproject_config.settings.file_resolver.respect_gitignore);
|
builder.standard_filters(resolver.respect_gitignore());
|
||||||
builder.hidden(false);
|
builder.hidden(false);
|
||||||
let walker = builder.build_parallel();
|
let walker = builder.build_parallel();
|
||||||
|
|
||||||
// Run the `WalkParallel` to collect all Python files.
|
// Run the `WalkParallel` to collect all Python files.
|
||||||
|
let is_hierarchical = resolver.is_hierarchical();
|
||||||
let error: std::sync::Mutex<Result<()>> = std::sync::Mutex::new(Ok(()));
|
let error: std::sync::Mutex<Result<()>> = std::sync::Mutex::new(Ok(()));
|
||||||
let resolver: RwLock<Resolver> = RwLock::new(resolver);
|
let resolver: RwLock<Resolver> = RwLock::new(resolver);
|
||||||
let files: std::sync::Mutex<Vec<Result<ResolvedFile, ignore::Error>>> =
|
let files: std::sync::Mutex<Vec<Result<ResolvedFile, ignore::Error>>> =
|
||||||
|
@ -346,7 +380,7 @@ pub fn python_files_in_path(
|
||||||
if entry.depth() > 0 {
|
if entry.depth() > 0 {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let resolver = resolver.read().unwrap();
|
let resolver = resolver.read().unwrap();
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
if let Some(file_name) = path.file_name() {
|
if let Some(file_name) = path.file_name() {
|
||||||
let file_path = Candidate::new(path);
|
let file_path = Candidate::new(path);
|
||||||
let file_basename = Candidate::new(file_name);
|
let file_basename = Candidate::new(file_name);
|
||||||
|
@ -374,7 +408,7 @@ pub fn python_files_in_path(
|
||||||
|
|
||||||
// Search for the `pyproject.toml` file in this directory, before we visit any
|
// Search for the `pyproject.toml` file in this directory, before we visit any
|
||||||
// of its contents.
|
// of its contents.
|
||||||
if pyproject_config.strategy.is_hierarchical() {
|
if is_hierarchical {
|
||||||
if let Ok(entry) = &result {
|
if let Ok(entry) = &result {
|
||||||
if entry
|
if entry
|
||||||
.file_type()
|
.file_type()
|
||||||
|
@ -416,7 +450,7 @@ pub fn python_files_in_path(
|
||||||
// Otherwise, check if the file is included.
|
// Otherwise, check if the file is included.
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let resolver = resolver.read().unwrap();
|
let resolver = resolver.read().unwrap();
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path);
|
||||||
if settings.file_resolver.include.is_match(path) {
|
if settings.file_resolver.include.is_match(path) {
|
||||||
debug!("Included path via `include`: {:?}", path);
|
debug!("Included path via `include`: {:?}", path);
|
||||||
Some(ResolvedFile::Nested(entry.into_path()))
|
Some(ResolvedFile::Nested(entry.into_path()))
|
||||||
|
@ -494,15 +528,14 @@ impl Ord for ResolvedFile {
|
||||||
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
|
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
|
||||||
pub fn python_file_at_path(
|
pub fn python_file_at_path(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
pyproject_config: &PyprojectConfig,
|
resolver: &mut Resolver,
|
||||||
transformer: &dyn ConfigurationTransformer,
|
transformer: &dyn ConfigurationTransformer,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
// Normalize the path (e.g., convert from relative to absolute).
|
// Normalize the path (e.g., convert from relative to absolute).
|
||||||
let path = fs::normalize_path(path);
|
let path = fs::normalize_path(path);
|
||||||
|
|
||||||
// Search for `pyproject.toml` files in all parent directories.
|
// Search for `pyproject.toml` files in all parent directories.
|
||||||
let mut resolver = Resolver::default();
|
if resolver.is_hierarchical() {
|
||||||
if pyproject_config.strategy.is_hierarchical() {
|
|
||||||
for ancestor in path.ancestors() {
|
for ancestor in path.ancestors() {
|
||||||
if let Some(pyproject) = settings_toml(ancestor)? {
|
if let Some(pyproject) = settings_toml(ancestor)? {
|
||||||
let (root, settings) =
|
let (root, settings) =
|
||||||
|
@ -514,18 +547,14 @@ pub fn python_file_at_path(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check exclusions.
|
// Check exclusions.
|
||||||
Ok(!is_file_excluded(&path, &resolver, pyproject_config))
|
Ok(!is_file_excluded(&path, resolver))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the given top-level [`Path`] should be excluded.
|
/// Return `true` if the given top-level [`Path`] should be excluded.
|
||||||
fn is_file_excluded(
|
fn is_file_excluded(path: &Path, resolver: &Resolver) -> bool {
|
||||||
path: &Path,
|
|
||||||
resolver: &Resolver,
|
|
||||||
pyproject_strategy: &PyprojectConfig,
|
|
||||||
) -> bool {
|
|
||||||
// TODO(charlie): Respect gitignore.
|
// TODO(charlie): Respect gitignore.
|
||||||
for path in path.ancestors() {
|
for path in path.ancestors() {
|
||||||
let settings = resolver.resolve(path, pyproject_strategy);
|
let settings = resolver.resolve(path);
|
||||||
if let Some(file_name) = path.file_name() {
|
if let Some(file_name) = path.file_name() {
|
||||||
let file_path = Candidate::new(path);
|
let file_path = Candidate::new(path);
|
||||||
let file_basename = Candidate::new(file_name);
|
let file_basename = Candidate::new(file_name);
|
||||||
|
@ -618,7 +647,6 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn rooted_exclusion() -> Result<()> {
|
fn rooted_exclusion() -> Result<()> {
|
||||||
let package_root = test_resource_path("package");
|
let package_root = test_resource_path("package");
|
||||||
let resolver = Resolver::default();
|
|
||||||
let pyproject_config = PyprojectConfig::new(
|
let pyproject_config = PyprojectConfig::new(
|
||||||
PyprojectDiscoveryStrategy::Hierarchical,
|
PyprojectDiscoveryStrategy::Hierarchical,
|
||||||
resolve_root_settings(
|
resolve_root_settings(
|
||||||
|
@ -628,20 +656,19 @@ mod tests {
|
||||||
)?,
|
)?,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
let resolver = Resolver::new(&pyproject_config);
|
||||||
// src/app.py should not be excluded even if it lives in a hierarchy that should
|
// src/app.py should not be excluded even if it lives in a hierarchy that should
|
||||||
// be excluded by virtue of the pyproject.toml having `resources/*` in
|
// be excluded by virtue of the pyproject.toml having `resources/*` in
|
||||||
// it.
|
// it.
|
||||||
assert!(!is_file_excluded(
|
assert!(!is_file_excluded(
|
||||||
&package_root.join("src/app.py"),
|
&package_root.join("src/app.py"),
|
||||||
&resolver,
|
&resolver,
|
||||||
&pyproject_config,
|
|
||||||
));
|
));
|
||||||
// However, resources/ignored.py should be ignored, since that `resources` is
|
// However, resources/ignored.py should be ignored, since that `resources` is
|
||||||
// beneath the package root.
|
// beneath the package root.
|
||||||
assert!(is_file_excluded(
|
assert!(is_file_excluded(
|
||||||
&package_root.join("resources/ignored.py"),
|
&package_root.join("resources/ignored.py"),
|
||||||
&resolver,
|
&resolver,
|
||||||
&pyproject_config,
|
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue