Escape template filenames in glob patterns (#16407)

## Summary

Fixes #9381. This PR fixes errors like 

```
Cause: error parsing glob '/Users/me/project/{{cookiecutter.project_dirname}}/__pycache__': nested alternate groups are not allowed
```

caused by glob special characters in filenames like
`{{cookiecutter.project_dirname}}`. When the user is matching that
directory exactly, they can use the workaround given by
https://github.com/astral-sh/ruff/issues/7959#issuecomment-1764751734,
but that doesn't work for a nested config file with relative paths. For
example, the directory tree in the reproduction repo linked
[here](https://github.com/astral-sh/ruff/issues/9381#issuecomment-2677696408):

```
.
├── README.md
├── hello.py
├── pyproject.toml
├── uv.lock
└── {{cookiecutter.repo_name}}
    ├── main.py
    ├── pyproject.toml
    └── tests
        └── maintest.py
```

where the inner `pyproject.toml` contains a relative glob:

```toml
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["F811"]
```

## Test Plan

A new CLI test in both the linter and formatter. The formatter test may
not be necessary because I didn't have to modify any additional code to
pass it, but the original report mentioned both `check` and `format`, so
I wanted to be sure both were fixed.
This commit is contained in:
Brent Westbrook 2025-03-03 09:29:58 -05:00 committed by GitHub
parent 4d92e20e81
commit d93ed293eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 195 additions and 70 deletions

View file

@ -30,7 +30,7 @@ use ruff_linter::settings::fix_safety_table::FixSafetyTable;
use ruff_linter::settings::rule_table::RuleTable;
use ruff_linter::settings::types::{
CompiledPerFileIgnoreList, CompiledPerFileTargetVersionList, ExtensionMapping, FilePattern,
FilePatternSet, OutputFormat, PerFileIgnore, PerFileTargetVersion, PreviewMode,
FilePatternSet, GlobPath, OutputFormat, PerFileIgnore, PerFileTargetVersion, PreviewMode,
RequiredVersion, UnsafeFixes,
};
use ruff_linter::settings::{LinterSettings, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX, TASK_TAGS};
@ -476,7 +476,7 @@ impl Configuration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -495,7 +495,7 @@ impl Configuration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -507,7 +507,7 @@ impl Configuration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -517,7 +517,7 @@ impl Configuration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -700,7 +700,7 @@ impl LintConfiguration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -1203,7 +1203,7 @@ impl FormatConfiguration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()
@ -1267,7 +1267,7 @@ impl AnalyzeConfiguration {
paths
.into_iter()
.map(|pattern| {
let absolute = fs::normalize_path_to(&pattern, project_root);
let absolute = GlobPath::normalize(&pattern, project_root);
FilePattern::User(pattern, absolute)
})
.collect()