Skip to content

Feature: Support generating subdirectory ruff.toml files with path-adjusted per-file-ignores #176

@Kilo59

Description

@Kilo59

Title

Support generating subdirectory ruff.toml files with path-adjusted per-file-ignores

Problem

Ruff supports hierarchical configuration via extend, which is useful for having different linter thresholds in subdirectories (e.g., relaxed max-args in tests/). However, per-file-ignores patterns inherited via extend break because ruff resolves glob patterns relative to the config file's directory, not the original source.

Example

project/
├── pyproject.toml          # root config
│   [tool.ruff.lint.per-file-ignores]
│   "tests/**/*.py" = ["S101", "TC001", "TC002"]
│
└── tests/
    └── ruff.toml           # extends root, overrides max-args
        extend = "../pyproject.toml"
        [lint.pylint]
        max-args = 10

When ruff processes tests/ruff.toml, the inherited "tests/**/*.py" pattern resolves to tests/tests/**/*.py — matching nothing. The TC001/TC002 ignores silently stop applying, which changes isort's import classification and cascades into I001 sorting violations across every existing test file.

Why extend-per-file-ignores doesn't fully solve it

Ruff shipped extend-per-file-ignores astral-sh/ruff#2558 which allows additive ignores. You can move the test ignores into tests/ruff.toml with "**/*.py" patterns. However, the TC001/TC002 ignores affect isort's import classification — adding them from a different config file changes the expected sort order for all existing test files, requiring a mass reformat of unrelated files.

The net result: there's no way to use a subdirectory ruff.toml for threshold overrides without either breaking per-file-ignores inheritance or causing import sorting churn across the entire test suite.

How ruff-sync could help

ruff-sync already handles merging upstream configs into local projects with path awareness. A natural extension would be generating subdirectory config files (like tests/ruff.toml) from a root config, with glob patterns automatically adjusted for the target directory.

Potential approaches

Option A: Path-rewriting on sync

When syncing a root config into a subdirectory ruff.toml, rewrite per-file-ignores patterns to be relative to the target directory. For example, "tests/**/*.py" in the root becomes "**/*.py" in tests/ruff.toml.

This could be a new command or flag:

# Generate tests/ruff.toml from root config with path-adjusted patterns
ruff-sync subdirectory tests/ --overrides tests/ruff-overrides.toml

Option B: Subdirectory config generation

A dedicated feature for generating subdirectory configs that:

  1. Takes the root config as the base
  2. Applies user-specified overrides (threshold changes, additional ignores)
  3. Rewrites all glob patterns to be relative to the target directory
  4. Outputs a self-contained ruff.toml that doesn't need extend

This avoids the extend path resolution bug entirely by producing a standalone config.

Option C: Exclude + extend-per-file-ignores coordination

When syncing, detect per-file-ignores patterns that reference the target subdirectory and automatically:

  1. Exclude them from the parent sync (so they don't break via extend)
  2. Add them to the child's extend-per-file-ignores with adjusted paths

Context

This came up while trying to set different pylint.max-args and mccabe.max-complexity thresholds for test files without disabling the rules entirely via per-file-ignores. The tests/ruff.toml approach is the documented way to do this, but it's broken in practice for any project that uses TC001/TC002 ignores on test files.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions