astral-sh / ruff

An extremely fast Python linter and code formatter, written in Rust.
https://docs.astral.sh/ruff
MIT License
28.79k stars 933 forks source link

Make flake8-type-checking's unsafe fixes not fix if a library is used in a doctest #12149

Open mikeweltevrede opened 5 days ago

mikeweltevrede commented 5 days ago

Hi all, curious to hear your opinion on this :) Please let me know if this needs to be filed on flake8-type-checking instead!

Ruleset

flake8-type-checking (TCH) with unsafe fixes on.

Ruff call and settings

Ruff version

0.4.8

Command invoked

pre-commit hooks:

repos:
  - repo: local
    hooks:
      - id:       ruff
        name:     Run ruff linter
        entry:    ruff
        args:     [ "--no-cache" ]
        language: python
        types:    [ file, python ]
        files:    .*\.py$

Relevant settings in pyproject.toml:

[tool.ruff]
line-length = 120
target-version = "py39"
fix = true  # Allow for ruff linting to fix some issues. Note that we control this behaviour in [tool.ruff.lint].

[tool.ruff.lint]
extend-select = [
    "TCH",  # flake8-type-checking | Allow for imports only used for type hinting | https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch
]
fixable = [
    "TCH001", "TCH002", "TCH003", "TCH004", "TCH005",   # flake8-type-checking
]
extend-safe-fixes = [
    "TCH001", "TCH002", "TCH003", "TCH004", "TCH005",   # flake8-type-checking
]

[tool.ruff.lint.flake8-type-checking]
quote-annotations = true
strict = true
exempt-modules = ["typing", "typing_extensions", "types"]

Explanation and Example

When allowing TCH to auto-fix, it will transform this...

from datetime import datetime

def my_func(dt: datetime):
    """Example function

    >>> my_func(dt=datetime(year=2024, month=7, day=2))
    'hello'
    """
    return "hello"

if __name__ == "__main__":
    import doctest
    doctest.testmod()

into ...

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from datetime import datetime

def my_func(dt: "datetime"):
    """Example function

    >>> my_func(dt=datetime(year=2024, month=7, day=2))
    'hello'
    """
    return "hello"

if __name__ == "__main__":
    import doctest
    doctest.testmod()

While this seems correct at first glance (no SyntaxError), notice that datetime is used in the doctest. It would be great if the ruff fixer can scan doctests as well for usage.

MichaReiser commented 4 days ago

It makes sense to me that Ruff shouldn't provide an autofix in this case because it breaks user code. Doing this would require that Ruff understands doctests and takes them into account when running the analysis. How this would work is an interesting question.