mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Respect --force-exclude for lint.exclude and format.exclude (#8393)
## Summary We typically avoid enforcing exclusions if a file was passed to Ruff directly on the CLI. However, we also allow `--force-exclude`, which ignores excluded files _even_ if they're passed to Ruff directly. This is really important for pre-commit, which always passes changed files -- we need to exclude files passed by pre-commit if they're in the `exclude` lists. Turns out the new `lint.exclude` and `format.exclude` settings weren't respecting `--force-exclude`. Closes https://github.com/astral-sh/ruff/issues/8391.
This commit is contained in:
parent
38358980f1
commit
1642f4dbd9
7 changed files with 139 additions and 37 deletions
|
@ -82,7 +82,7 @@ pub(crate) fn check(
|
||||||
|
|
||||||
let settings = resolver.resolve(path, pyproject_config);
|
let settings = resolver.resolve(path, pyproject_config);
|
||||||
|
|
||||||
if !resolved_file.is_root()
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
&& match_exclusion(
|
&& match_exclusion(
|
||||||
resolved_file.path(),
|
resolved_file.path(),
|
||||||
resolved_file.file_name(),
|
resolved_file.file_name(),
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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 {
|
||||||
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, pyproject_config, overrides)? {
|
||||||
return Ok(Diagnostics::default());
|
return Ok(Diagnostics::default());
|
||||||
|
@ -31,6 +32,7 @@ pub(crate) fn check_stdin(
|
||||||
return Ok(Diagnostics::default());
|
return Ok(Diagnostics::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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, &pyproject_config.settings.linter.namespace_packages)
|
||||||
});
|
});
|
||||||
|
|
|
@ -117,14 +117,14 @@ pub(crate) fn format(
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
let settings = resolver.resolve(path, &pyproject_config);
|
||||||
|
|
||||||
// Ignore files that are excluded from formatting
|
// Ignore files that are excluded from formatting
|
||||||
if !resolved_file.is_root()
|
if (settings.file_resolver.force_exclude || !resolved_file.is_root())
|
||||||
&& match_exclusion(
|
&& match_exclusion(
|
||||||
path,
|
path,
|
||||||
resolved_file.file_name(),
|
resolved_file.file_name(),
|
||||||
&resolved_settings.formatter.exclude,
|
&settings.formatter.exclude,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
|
@ -139,13 +139,7 @@ pub(crate) fn format(
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
match catch_unwind(|| {
|
match catch_unwind(|| {
|
||||||
format_path(
|
format_path(path, &settings.formatter, source_type, mode, cache)
|
||||||
path,
|
|
||||||
&resolved_settings.formatter,
|
|
||||||
source_type,
|
|
||||||
mode,
|
|
||||||
cache,
|
|
||||||
)
|
|
||||||
}) {
|
}) {
|
||||||
Ok(inner) => inner.map(|result| FormatPathResult {
|
Ok(inner) => inner.map(|result| FormatPathResult {
|
||||||
path: resolved_file.path().to_path_buf(),
|
path: resolved_file.path().to_path_buf(),
|
||||||
|
|
|
@ -31,6 +31,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||||
|
|
||||||
let mode = FormatMode::from_cli(cli);
|
let mode = FormatMode::from_cli(cli);
|
||||||
|
|
||||||
|
if pyproject_config.settings.file_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, &pyproject_config, overrides)? {
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
|
@ -44,6 +45,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let path = cli.stdin_filename.as_deref();
|
let path = cli.stdin_filename.as_deref();
|
||||||
|
|
||||||
|
|
|
@ -188,6 +188,73 @@ OTHER = "OTHER"
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn force_exclude() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-exclude = ["out"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["test.py", "generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("main.py"),
|
||||||
|
r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Excluded file but passed to the CLI directly, should be formatted
|
||||||
|
let test_path = tempdir.path().join("test.py");
|
||||||
|
fs::write(
|
||||||
|
&test_path,
|
||||||
|
r#"
|
||||||
|
def say_hy(name: str):
|
||||||
|
print(f"Hy {name}")"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
tempdir.path().join("generated.py"),
|
||||||
|
r#"NUMBERS = [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||||
|
]
|
||||||
|
OTHER = "OTHER"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let out_dir = tempdir.path().join("out");
|
||||||
|
fs::create_dir(&out_dir)?;
|
||||||
|
|
||||||
|
fs::write(out_dir.join("a.py"), "a = a")?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--no-cache", "--force-exclude", "--check", "--config"])
|
||||||
|
.arg(ruff_toml.file_name().unwrap())
|
||||||
|
// Explicitly pass test.py, should be respect the `format.exclude` when `--force-exclude` is present
|
||||||
|
.arg(test_path.file_name().unwrap())
|
||||||
|
// Format all other files in the directory, should respect the `exclude` and `format.exclude` options
|
||||||
|
.arg("."), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
Would reformat: main.py
|
||||||
|
1 file would be reformatted
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exclude_stdin() -> Result<()> {
|
fn exclude_stdin() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
@ -209,6 +276,43 @@ exclude = ["generated.py"]
|
||||||
.pass_stdin(r#"
|
.pass_stdin(r#"
|
||||||
from test import say_hy
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
"#), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
say_hy("dear Ruff contributor")
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn force_exclude_stdin() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||||
|
fs::write(
|
||||||
|
&ruff_toml,
|
||||||
|
r#"
|
||||||
|
extend-select = ["B", "Q"]
|
||||||
|
ignore = ["Q000", "Q001", "Q002", "Q003"]
|
||||||
|
|
||||||
|
[format]
|
||||||
|
exclude = ["generated.py"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.current_dir(tempdir.path())
|
||||||
|
.args(["format", "--config", &ruff_toml.file_name().unwrap().to_string_lossy(), "--stdin-filename", "generated.py", "--force-exclude", "-"])
|
||||||
|
.pass_stdin(r#"
|
||||||
|
from test import say_hy
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
say_hy("dear Ruff contributor")
|
say_hy("dear Ruff contributor")
|
||||||
"#), @r###"
|
"#), @r###"
|
||||||
|
|
|
@ -262,9 +262,13 @@ from test import say_hy
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
say_hy("dear Ruff contributor")
|
say_hy("dear Ruff contributor")
|
||||||
"#), @r###"
|
"#), @r###"
|
||||||
success: true
|
success: false
|
||||||
exit_code: 0
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
generated.py:4:16: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
generated.py:5:12: Q000 [*] Double quotes found but single quotes preferred
|
||||||
|
Found 2 errors.
|
||||||
|
[*] 2 fixable with the `--fix` option.
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"###);
|
||||||
|
|
|
@ -483,10 +483,6 @@ pub fn python_file_at_path(
|
||||||
pyproject_config: &PyprojectConfig,
|
pyproject_config: &PyprojectConfig,
|
||||||
transformer: &dyn ConfigurationTransformer,
|
transformer: &dyn ConfigurationTransformer,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if !pyproject_config.settings.file_resolver.force_exclude {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue