Compare commits
1 Commits
v0.0.283
...
collect_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f2979ed66 |
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/*
|
||||
- scripts/check_ecosystem.py
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -56,7 +56,6 @@ jobs:
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
- crates/ruff_dev/**
|
||||
- scripts/*
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -339,5 +338,8 @@ jobs:
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
- name: "Remove checkouts from cache"
|
||||
run: rm -r target/progress_projects
|
||||
# CPython is not black formatted, so we run only the stability check
|
||||
- name: "Clone CPython 3.10"
|
||||
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
|
||||
- name: "Check CPython stability"
|
||||
run: cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.283
|
||||
|
||||
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
|
||||
|
||||
Previously, when a target Python version was not specified, Ruff would use a default of Python 3.10. However, it is safer to default to an _older_ Python version to avoid assuming the availability of new features. We now default to the oldest supported Python version which is currently Python 3.8.
|
||||
|
||||
(We still support Python 3.7 but since [it has reached EOL](https://devguide.python.org/versions/#unsupported-versions) we've decided not to make it the default here.)
|
||||
|
||||
## 0.0.277
|
||||
|
||||
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||
|
||||
@@ -131,6 +131,7 @@ At time of writing, the repository includes the following crates:
|
||||
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
|
||||
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
|
||||
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
|
||||
intermediate representation for each node, which `ruff_formatter` prints based on the configured
|
||||
line length.
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -800,7 +800,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2042,7 +2042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2141,7 +2141,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.283"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.283
|
||||
rev: v0.0.282
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -211,8 +211,8 @@ line-length = 88
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
# Assume Python 3.10.
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.mccabe]
|
||||
# Unlike Flake8, default to a complexity level of 10.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.282"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.282"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -240,16 +240,12 @@ def foo(f=lambda x: print(x)):
|
||||
|
||||
from collections import abc
|
||||
from typing import Annotated, Dict, Optional, Sequence, Union, Set
|
||||
import typing_extensions
|
||||
|
||||
|
||||
def immutable_annotations(
|
||||
a: Sequence[int] | None = [],
|
||||
b: Optional[abc.Mapping[int, int]] = {},
|
||||
c: Annotated[Union[abc.Set[str], abc.Sized], "annotation"] = set(),
|
||||
d: typing_extensions.Annotated[
|
||||
Union[abc.Set[str], abc.Sized], "annotation"
|
||||
] = set(),
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -258,6 +254,5 @@ def mutable_annotations(
|
||||
a: list[int] | None = [],
|
||||
b: Optional[Dict[int, int]] = {},
|
||||
c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -74,10 +74,3 @@ try:
|
||||
except (ValueError, binascii.Error):
|
||||
# binascii.Error is a subclass of ValueError.
|
||||
pass
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/6412
|
||||
try:
|
||||
pass
|
||||
except (ValueError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -3,6 +3,3 @@ import logging
|
||||
name = "world"
|
||||
logging.info(f"Hello {name}")
|
||||
logging.log(logging.INFO, f"Hello {name}")
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
_LOGGER.info(f"{__name__}")
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import typing
|
||||
|
||||
# Shouldn't affect non-union field types.
|
||||
field1: str
|
||||
|
||||
# Should emit for duplicate field types.
|
||||
field2: str | str # PYI016: Duplicate union member `str`
|
||||
|
||||
|
||||
# Should emit for union types in arguments.
|
||||
def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
print(arg1)
|
||||
|
||||
|
||||
# Should emit for unions in return types.
|
||||
def func2() -> str | str: # PYI016: Duplicate union member `str`
|
||||
return "my string"
|
||||
|
||||
|
||||
# Should emit in longer unions, even if not directly adjacent.
|
||||
field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
@@ -32,55 +33,3 @@ field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
|
||||
# Should emit for nested unions.
|
||||
field11: dict[int | int, str]
|
||||
|
||||
# Should emit for unions with more than two cases
|
||||
field12: int | int | int # Error
|
||||
field13: int | int | int | int # Error
|
||||
|
||||
# Should emit for unions with more than two cases, even if not directly adjacent
|
||||
field14: int | int | str | int # Error
|
||||
|
||||
# Should emit for duplicate literal types; also covered by PYI030
|
||||
field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||
|
||||
# Shouldn't emit if in new parent type
|
||||
field16: int | dict[int, str] # OK
|
||||
|
||||
# Shouldn't emit if not in a union parent
|
||||
field17: dict[int, int] # OK
|
||||
|
||||
# Should emit in cases with newlines
|
||||
field18: typing.Union[
|
||||
set[
|
||||
int # foo
|
||||
],
|
||||
set[
|
||||
int # bar
|
||||
],
|
||||
] # Error, newline and comment will not be emitted in message
|
||||
|
||||
# Should emit in cases with `typing.Union` instead of `|`
|
||||
field19: typing.Union[int, int] # Error
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field20: typing.Union[int, typing.Union[int, str]] # Error
|
||||
|
||||
# Should emit in cases with mixed `typing.Union` and `|`
|
||||
field21: typing.Union[int, int | str] # Error
|
||||
|
||||
# Should emit only once in cases with multiple nested `typing.Union`
|
||||
field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
|
||||
# Should emit in cases with newlines
|
||||
field23: set[ # foo
|
||||
int] | set[int]
|
||||
|
||||
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
|
||||
@@ -74,13 +74,3 @@ field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
# Should emit in cases with newlines
|
||||
field23: set[ # foo
|
||||
int] | set[int]
|
||||
|
||||
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit twice (once for each `int` in the nested union, both of which are
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
|
||||
@@ -14,10 +14,6 @@ class BadClass:
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
@@ -14,10 +14,6 @@ class BadClass:
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"execution_count": null,
|
||||
"cell_type": "code",
|
||||
"id": "1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%timeit\n", "print('hello world')"]
|
||||
}
|
||||
@@ -25,23 +25,6 @@
|
||||
"def foo():\n",
|
||||
" pass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "16214f6f-bb32-4594-81be-79fb27c6ec92",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"%matplotlib \\\n",
|
||||
" --inline\n",
|
||||
"\n",
|
||||
"import math\n",
|
||||
"import abc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -27,23 +27,6 @@
|
||||
"def foo():\n",
|
||||
" pass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6d6c55c6-4a34-4662-914b-4ee11c9c24a5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"%matplotlib \\\n",
|
||||
" --inline\n",
|
||||
"\n",
|
||||
"import abc\n",
|
||||
"import math"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eab4754a-d6df-4b41-8ee8-7e23aef440f9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"\n",
|
||||
"%matplotlib inline\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"_ = math.pi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2b0e2986-1b87-4bb6-9b1d-c11ca1decd87",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%timeit\n",
|
||||
"import sys"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cad32845-44f9-4a53-8b8c-a6b1bb3f3378",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"\n",
|
||||
"%matplotlib inline\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_ = math.pi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d7b8e967-8b4a-493b-b6f7-d5cecfb3a5c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%timeit\n",
|
||||
"import sys"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0efffbc-85f1-4513-bf49-5387ec3a2a4e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" foo1 = %matplotlib --list\n",
|
||||
" foo2: list[str] = %matplotlib --list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6e0b2b50-43f2-4f59-951d-9404dd560ae4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" bar1 = !pwd\n",
|
||||
" bar2: str = !pwd"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "24426ef2-046c-453e-b809-05b56e7355e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" %matplotlib --list\n",
|
||||
" %matplotlib --list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3d98fdae-b86b-476e-b4db-9d3ce5562682",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" !pwd\n",
|
||||
" !pwd"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -634,8 +634,3 @@ def starts_with_this():
|
||||
@expect('D404: First word of the docstring should not be "This"')
|
||||
def starts_with_space_then_this():
|
||||
""" This is a docstring that starts with a space.""" # noqa: D210
|
||||
|
||||
|
||||
class SameLine: """This is a docstring on the same line"""
|
||||
|
||||
def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
@@ -19,10 +19,6 @@ foo in foo
|
||||
|
||||
foo not in foo
|
||||
|
||||
id(foo) == id(foo)
|
||||
|
||||
len(foo) == len(foo)
|
||||
|
||||
# Non-errors.
|
||||
"foo" == "foo" # This is flagged by `comparison-of-constant` instead.
|
||||
|
||||
@@ -47,11 +43,3 @@ foo is not bar
|
||||
foo in bar
|
||||
|
||||
foo not in bar
|
||||
|
||||
x(foo) == y(foo)
|
||||
|
||||
id(foo) == id(bar)
|
||||
|
||||
id(foo, bar) == id(foo, bar)
|
||||
|
||||
id(foo, bar=1) == id(foo, bar=1)
|
||||
|
||||
@@ -32,30 +32,3 @@ print(
|
||||
)
|
||||
|
||||
'{' '0}'.format(1)
|
||||
|
||||
args = list(range(10))
|
||||
kwargs = {x: x for x in range(10)}
|
||||
|
||||
"{0}".format(*args)
|
||||
|
||||
"{0}".format(**kwargs)
|
||||
|
||||
"{0}_{1}".format(*args)
|
||||
|
||||
"{0}_{1}".format(1, *args)
|
||||
|
||||
"{0}_{1}".format(1, 2, *args)
|
||||
|
||||
"{0}_{1}".format(*args, 1, 2)
|
||||
|
||||
"{0}_{1}_{2}".format(1, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)
|
||||
|
||||
"{1}_{0}".format(1, 2, *args)
|
||||
|
||||
"{1}_{0}".format(1, 2)
|
||||
|
||||
@@ -15,17 +15,3 @@ f"{0}".format(1)
|
||||
print(f"{0}".format(1))
|
||||
|
||||
''.format(1)
|
||||
|
||||
'{1} {0}'.format(*args)
|
||||
|
||||
"{1}_{0}".format(*args, 1)
|
||||
|
||||
"{1}_{0}".format(*args, 1, 2)
|
||||
|
||||
"{1}_{0}".format(1, **kwargs)
|
||||
|
||||
"{1}_{0}".format(1, foo=2)
|
||||
|
||||
"{1}_{0}".format(1, 2, **kwargs)
|
||||
|
||||
"{1}_{0}".format(1, 2, foo=3, bar=4)
|
||||
|
||||
28
crates/ruff/resources/test/fixtures/pyupgrade/UP030_2.py
vendored
Normal file
28
crates/ruff/resources/test/fixtures/pyupgrade/UP030_2.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# These SHOULD change
|
||||
|
||||
args = list(range(10))
|
||||
kwargs = {x: x for x in range(10)}
|
||||
|
||||
"{0}".format(*args)
|
||||
|
||||
"{0}".format(**kwargs)
|
||||
|
||||
"{0}_{1}".format(*args)
|
||||
|
||||
"{0}_{1}".format(1, *args)
|
||||
|
||||
"{1}_{0}".format(*args)
|
||||
|
||||
"{1}_{0}".format(1, *args)
|
||||
|
||||
"{0}_{1}".format(1, 2, *args)
|
||||
|
||||
"{0}_{1}".format(*args, 1, 2)
|
||||
|
||||
"{0}_{1}_{2}".format(1, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, 3, **kwargs)
|
||||
|
||||
"{0}_{1}_{2}".format(1, 2, 3, *args, **kwargs)
|
||||
@@ -106,7 +106,3 @@ print('Hello %(arg)s' % bar['bop'])
|
||||
"""
|
||||
% (x,)
|
||||
)
|
||||
|
||||
"%s" % (
|
||||
x, # comment
|
||||
)
|
||||
|
||||
@@ -198,7 +198,3 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
).format(a=1)
|
||||
|
||||
"{}".format(**c)
|
||||
|
||||
"{}".format(
|
||||
1 # comment
|
||||
)
|
||||
|
||||
@@ -46,35 +46,6 @@ from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, Con
|
||||
if True: from collections import (
|
||||
Mapping, Counter)
|
||||
|
||||
# Bad imports from PYI027 that are now handled by PYI022 (UP035)
|
||||
from typing import ContextManager
|
||||
from typing import OrderedDict
|
||||
from typing_extensions import OrderedDict
|
||||
from typing import Callable
|
||||
from typing import ByteString
|
||||
from typing import Container
|
||||
from typing import Hashable
|
||||
from typing import ItemsView
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import KeysView
|
||||
from typing import Mapping
|
||||
from typing import MappingView
|
||||
from typing import MutableMapping
|
||||
from typing import MutableSequence
|
||||
from typing import MutableSet
|
||||
from typing import Sequence
|
||||
from typing import Sized
|
||||
from typing import ValuesView
|
||||
from typing import Awaitable
|
||||
from typing import AsyncIterator
|
||||
from typing import AsyncIterable
|
||||
from typing import Coroutine
|
||||
from typing import Collection
|
||||
from typing import AsyncGenerator
|
||||
from typing import Reversible
|
||||
from typing import Generator
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
|
||||
@@ -5,42 +5,11 @@ from typing import TypeAlias
|
||||
x: typing.TypeAlias = int
|
||||
x: TypeAlias = int
|
||||
|
||||
# UP040 simple generic
|
||||
|
||||
# UP040 with generics (todo)
|
||||
T = typing.TypeVar["T"]
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 call style generic
|
||||
T = typing.TypeVar("T")
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 bounded generic (todo)
|
||||
T = typing.TypeVar("T", bound=int)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
T = typing.TypeVar("T", int, str)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 contravariant generic (todo)
|
||||
T = typing.TypeVar("T", contravariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 covariant generic (todo)
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 in class scope
|
||||
T = typing.TypeVar["T"]
|
||||
class Foo:
|
||||
# reference to global variable
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# reference to class variable
|
||||
TCLS = typing.TypeVar["TCLS"]
|
||||
y: typing.TypeAlias = list[TCLS]
|
||||
|
||||
# UP040 wont add generics in fix
|
||||
T = typing.TypeVar(*args)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# OK
|
||||
x: TypeAlias
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# ruff: noqa: RUF100
|
||||
|
||||
import os # noqa: F401
|
||||
|
||||
print(os.sep)
|
||||
@@ -33,7 +33,7 @@ impl<'a, T: Codegen<'a>> CodegenStylist<'a> for T {
|
||||
///
|
||||
/// Returns `Ok(None)` if the statement is empty after removing the imports.
|
||||
pub(crate) fn remove_imports<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
@@ -45,20 +45,27 @@ pub(crate) fn remove_imports<'a>(
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let aliases = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
names
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for member in member_names {
|
||||
if member == "*" {
|
||||
for import in imports {
|
||||
let qualified_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if import == qualified_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", member);
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", import);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
@@ -75,10 +82,30 @@ pub(crate) fn remove_imports<'a>(
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for member in member_names {
|
||||
let alias_index = aliases
|
||||
.iter()
|
||||
.position(|alias| member == compose_module_path(&alias.name));
|
||||
for import in imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
@@ -112,7 +139,7 @@ pub(crate) fn remove_imports<'a>(
|
||||
///
|
||||
/// Returns the modified import statement.
|
||||
pub(crate) fn retain_imports(
|
||||
member_names: &[&str],
|
||||
imports: &[&str],
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
@@ -124,11 +151,14 @@ pub(crate) fn retain_imports(
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let aliases = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
names
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases");
|
||||
}
|
||||
@@ -140,9 +170,28 @@ pub(crate) fn retain_imports(
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
aliases.retain(|alias| {
|
||||
member_names
|
||||
.iter()
|
||||
.any(|member| *member == compose_module_path(&alias.name))
|
||||
imports.iter().any(|import| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == *import
|
||||
})
|
||||
});
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, ExceptHandler, Expr, Keyword, PySourceType, Ranged, Stmt,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{lexer, AsMode};
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
@@ -58,14 +56,14 @@ pub(crate) fn delete_stmt(
|
||||
|
||||
/// Generate a `Fix` to remove the specified imports from an `import` statement.
|
||||
pub(crate) fn remove_unused_imports<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
unused_imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
) -> Result<Edit> {
|
||||
match codemods::remove_imports(member_names, stmt, locator, stylist)? {
|
||||
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
|
||||
None => Ok(delete_stmt(stmt, parent, locator, indexer)),
|
||||
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
|
||||
}
|
||||
@@ -90,7 +88,6 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
if arguments.keywords.len() + arguments.args.len() > 1 {
|
||||
@@ -109,7 +106,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
@@ -138,7 +135,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
// previous comma to the end of the argument.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
@@ -179,13 +176,16 @@ fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||
fn is_lone_child(child: &Stmt, parent: &Stmt) -> bool {
|
||||
match parent {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. })
|
||||
| Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. }) => {
|
||||
| Stmt::With(ast::StmtWith { body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
if is_only(body, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, orelse, .. })
|
||||
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
if is_only(body, child) || is_only(orelse, child) {
|
||||
return true;
|
||||
|
||||
@@ -11,18 +11,21 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
for snapshot in for_loops {
|
||||
checker.semantic.restore(snapshot);
|
||||
|
||||
let Stmt::For(ast::StmtFor {
|
||||
if let Stmt::For(ast::StmtFor {
|
||||
target, iter, body, ..
|
||||
}) = checker.semantic.current_statement()
|
||||
else {
|
||||
unreachable!("Expected Stmt::For");
|
||||
};
|
||||
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target, iter, body, ..
|
||||
}) = &checker.semantic.stmt()
|
||||
{
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(checker, target, iter);
|
||||
}
|
||||
} else {
|
||||
unreachable!("Expected Expr::For | Expr::AsyncFor");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
|
||||
@@ -36,7 +37,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||
// used at runtime, then by default, we avoid flagging any other
|
||||
// imports from that model as typing-only.
|
||||
let enforce_typing_imports = !checker.source_type.is_stub()
|
||||
let enforce_typing_imports = !checker.is_stub
|
||||
&& checker.any_enabled(&[
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
@@ -111,11 +112,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
@@ -171,25 +168,16 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(statement_id) = shadowed.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If this is an overloaded function, abort.
|
||||
if shadowed.kind.is_function_definition() {
|
||||
if checker
|
||||
.semantic
|
||||
.statement(statement_id)
|
||||
.as_function_def_stmt()
|
||||
.is_some_and(|function| {
|
||||
visibility::is_overload(
|
||||
&function.decorator_list,
|
||||
&checker.semantic,
|
||||
)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(
|
||||
checker.semantic.stmts[shadowed.source.unwrap()],
|
||||
),
|
||||
&checker.semantic,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Only enforce cross-scope shadowing for imports.
|
||||
@@ -207,11 +195,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
@@ -247,7 +231,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
|
||||
) {
|
||||
if checker.enabled(Rule::UnusedVariable) {
|
||||
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
|
||||
}
|
||||
@@ -256,7 +243,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pyflakes::rules::unused_annotation(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::UnusedFunctionArgument,
|
||||
@@ -273,7 +260,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Module
|
||||
) {
|
||||
if enforce_typing_imports {
|
||||
let runtime_imports: Vec<&Binding> = checker
|
||||
.semantic
|
||||
|
||||
@@ -30,7 +30,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::MissingTypeKwargs,
|
||||
Rule::MissingTypeSelf,
|
||||
]);
|
||||
let enforce_stubs = checker.source_type.is_stub() && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs = checker.is_stub && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
|
||||
let enforce_docstrings = checker.any_enabled(&[
|
||||
Rule::BlankLineAfterLastSection,
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -44,7 +44,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP604Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py310
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
// Ex) list[...]
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
@@ -80,9 +80,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::RedundantLiteralUnion,
|
||||
Rule::UnnecessaryTypeUnion,
|
||||
]) {
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// Avoid duplicate checks if the parent is an `Union[...]` since these rules
|
||||
// traverse nested unions.
|
||||
if !checker.semantic.in_nested_union() {
|
||||
let is_unchecked_union = checker
|
||||
.semantic
|
||||
.expr_grandparent()
|
||||
.and_then(Expr::as_subscript_expr)
|
||||
.map_or(true, |parent| {
|
||||
!checker.semantic.match_typing_expr(&parent.value, "Union")
|
||||
});
|
||||
|
||||
if is_unchecked_union {
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
@@ -168,7 +176,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
typing::to_pep585_generic(expr, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -179,7 +187,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP585Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -198,16 +206,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||
if checker.semantic.current_scope().kind.is_function() {
|
||||
if checker.semantic.scope().kind.is_any_function() {
|
||||
// Ignore globals.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scope()
|
||||
.get(id)
|
||||
.is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
})
|
||||
{
|
||||
if !checker.semantic.scope().get(id).is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
}) {
|
||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||
checker, expr, id,
|
||||
);
|
||||
@@ -216,7 +219,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
&checker.semantic.current_scope().kind
|
||||
&checker.semantic.scope().kind
|
||||
{
|
||||
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
||||
checker,
|
||||
@@ -227,7 +230,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
|
||||
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
if matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
||||
pep8_naming::rules::mixed_case_variable_in_global_scope(
|
||||
checker, expr, id,
|
||||
);
|
||||
@@ -240,7 +243,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_attribute_shadowing(
|
||||
checker, class_def, id, *range,
|
||||
@@ -269,7 +272,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -282,7 +285,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP585Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -338,8 +341,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
// pylint
|
||||
Rule::BadStringFormatCharacter,
|
||||
// pyflakes
|
||||
Rule::StringDotFormatInvalidFormat,
|
||||
Rule::StringDotFormatExtraNamedArguments,
|
||||
@@ -402,7 +403,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::FormatLiterals) {
|
||||
pyupgrade::rules::format_literals(checker, &summary, call);
|
||||
pyupgrade::rules::format_literals(checker, &summary, expr);
|
||||
}
|
||||
if checker.enabled(Rule::FString) {
|
||||
pyupgrade::rules::f_strings(
|
||||
@@ -665,7 +666,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_comprehensions::rules::unnecessary_map(
|
||||
checker,
|
||||
expr,
|
||||
checker.semantic.current_expression_parent(),
|
||||
checker.semantic.expr_parent(),
|
||||
func,
|
||||
args,
|
||||
);
|
||||
@@ -915,7 +916,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::await_outside_async(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::FString(ast::ExprFString { values, range: _ }) => {
|
||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
||||
if checker.enabled(Rule::FStringMissingPlaceholders) {
|
||||
pyflakes::rules::f_string_missing_placeholders(expr, values, checker);
|
||||
}
|
||||
@@ -1063,7 +1064,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}) => {
|
||||
// Ex) `str | None`
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
@@ -1076,23 +1077,29 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// Avoid duplicate checks if the parent is an `|` since these rules
|
||||
// traverse nested unions.
|
||||
if !checker.semantic.in_nested_union() {
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
let is_unchecked_union = !matches!(
|
||||
checker.semantic.expr_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
..
|
||||
}))
|
||||
);
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
&& is_unchecked_union
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
@@ -1203,7 +1210,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
if checker.is_stub && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
@@ -1212,7 +1219,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
if checker.is_stub && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
@@ -1240,7 +1247,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
|
||||
ruff::rules::implicit_optional(checker, parameters);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::BreakOutsideLoop) {
|
||||
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
&mut checker.semantic.parents().skip(1),
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -63,21 +63,29 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ContinueOutsideLoop) {
|
||||
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
&mut checker.semantic.parents().skip(1),
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
range: _,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
..
|
||||
}) => {
|
||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
flake8_django::rules::non_leading_receiver_decorator(checker, decorator_list);
|
||||
@@ -105,7 +113,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if let Some(diagnostic) =
|
||||
pep8_naming::rules::invalid_first_argument_name_for_class_method(
|
||||
checker,
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
@@ -117,7 +125,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
|
||||
checker,
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
@@ -125,7 +133,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::PassStatementStubBody) {
|
||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||
}
|
||||
@@ -143,11 +151,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_pyi::rules::non_self_return_type(
|
||||
checker,
|
||||
stmt,
|
||||
*is_async,
|
||||
name,
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
parameters,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::CustomTypeVarReturnType) {
|
||||
@@ -160,27 +168,30 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
type_params.as_ref(),
|
||||
);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py311
|
||||
{
|
||||
if checker.is_stub || checker.settings.target_version >= PythonVersion::Py311 {
|
||||
if checker.enabled(Rule::NoReturnArgumentAnnotationInStub) {
|
||||
flake8_pyi::rules::no_return_argument_annotation(checker, parameters);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BadExitAnnotation) {
|
||||
flake8_pyi::rules::bad_exit_annotation(checker, *is_async, name, parameters);
|
||||
flake8_pyi::rules::bad_exit_annotation(
|
||||
checker,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
name,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::DunderFunctionName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
stmt,
|
||||
name,
|
||||
&checker.settings.pep8_naming.ignore_names,
|
||||
@@ -335,7 +346,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::YieldInForLoop) {
|
||||
pyupgrade::rules::yield_in_for_loop(checker, stmt);
|
||||
}
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_method_shadowing(
|
||||
checker,
|
||||
@@ -401,7 +412,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
body,
|
||||
);
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::DjangoModelWithoutDunderStr) {
|
||||
flake8_django::rules::model_without_dunder_str(checker, class_def);
|
||||
}
|
||||
@@ -442,7 +453,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::AbstractBaseClassWithoutAbstractMethod,
|
||||
Rule::EmptyMethodWithoutAbstractDecorator,
|
||||
@@ -456,7 +467,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::PassStatementStubBody) {
|
||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||
}
|
||||
@@ -558,7 +569,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
alias,
|
||||
);
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
}
|
||||
@@ -733,7 +744,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::FutureAnnotationsInStub) {
|
||||
flake8_pyi::rules::from_future_import(checker, import_from);
|
||||
}
|
||||
@@ -753,7 +764,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
} else if &alias.name == "*" {
|
||||
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
||||
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
if !matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
||||
name: helpers::format_import_from(level, module),
|
||||
@@ -878,7 +889,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
}
|
||||
@@ -969,7 +980,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_simplify::rules::nested_if_statements(
|
||||
checker,
|
||||
if_,
|
||||
checker.semantic.current_statement_parent(),
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::IfWithSameArms) {
|
||||
@@ -991,7 +1002,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
tryceratops::rules::type_check_without_type_error(
|
||||
checker,
|
||||
if_,
|
||||
checker.semantic.current_statement_parent(),
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::OutdatedVersionBlock) {
|
||||
@@ -1002,7 +1013,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
Rule::PatchVersionComparison,
|
||||
@@ -1084,7 +1095,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pygrep_hooks::rules::non_existent_mock_method(checker, test);
|
||||
}
|
||||
}
|
||||
Stmt::With(with_ @ ast::StmtWith { items, body, .. }) => {
|
||||
Stmt::With(ast::StmtWith { items, body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
||||
if checker.enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception(checker, items);
|
||||
}
|
||||
@@ -1094,8 +1106,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MultipleWithStatements) {
|
||||
flake8_simplify::rules::multiple_with_statements(
|
||||
checker,
|
||||
with_,
|
||||
checker.semantic.current_statement_parent(),
|
||||
stmt,
|
||||
body,
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedefinedLoopName) {
|
||||
@@ -1119,6 +1132,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target,
|
||||
body,
|
||||
iter,
|
||||
orelse,
|
||||
..
|
||||
}) => {
|
||||
if checker.any_enabled(&[Rule::UnusedLoopControlVariable, Rule::IncorrectDictIterator])
|
||||
{
|
||||
@@ -1305,7 +1325,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::TypeBivariance) {
|
||||
pylint::rules::type_bivariance(checker, value);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnprefixedTypeParam,
|
||||
Rule::AssignmentDefaultInStub,
|
||||
@@ -1316,8 +1336,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
// Ignore assignments in function bodies; those are covered by other rules.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.any(|scope| scope.kind.is_function())
|
||||
.scopes()
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
if checker.enabled(Rule::UnprefixedTypeParam) {
|
||||
flake8_pyi::rules::prefix_type_params(checker, value, targets);
|
||||
@@ -1375,14 +1395,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NonPEP695TypeAlias) {
|
||||
pyupgrade::rules::non_pep695_type_alias(checker, assign_stmt);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if let Some(value) = value {
|
||||
if checker.enabled(Rule::AssignmentDefaultInStub) {
|
||||
// Ignore assignments in function bodies; those are covered by other rules.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.any(|scope| scope.kind.is_function())
|
||||
.scopes()
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
flake8_pyi::rules::annotated_assignment_default_in_stub(
|
||||
checker, target, value, annotation,
|
||||
|
||||
@@ -39,13 +39,11 @@ use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, to_module_path,
|
||||
};
|
||||
use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::str::trailing_quote;
|
||||
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||
use ruff_python_ast::{helpers, str, visitor};
|
||||
use ruff_python_codegen::{Generator, Quote, Stylist};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
||||
@@ -55,6 +53,7 @@ use ruff_python_semantic::{
|
||||
ModuleKind, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::checkers::ast::deferred::Deferred;
|
||||
@@ -76,8 +75,8 @@ pub(crate) struct Checker<'a> {
|
||||
package: Option<&'a Path>,
|
||||
/// The module representation of the current file (e.g., `foo.bar`).
|
||||
module_path: Option<&'a [String]>,
|
||||
/// The [`PySourceType`] of the current file.
|
||||
pub(crate) source_type: PySourceType,
|
||||
/// Whether the current file is a stub (`.pyi`) file.
|
||||
is_stub: bool,
|
||||
/// The [`flags::Noqa`] for the current analysis (i.e., whether to respect suppression
|
||||
/// comments).
|
||||
noqa: flags::Noqa,
|
||||
@@ -119,7 +118,6 @@ impl<'a> Checker<'a> {
|
||||
stylist: &'a Stylist,
|
||||
indexer: &'a Indexer,
|
||||
importer: Importer<'a>,
|
||||
source_type: PySourceType,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
@@ -128,7 +126,7 @@ impl<'a> Checker<'a> {
|
||||
path,
|
||||
package,
|
||||
module_path: module.path(),
|
||||
source_type,
|
||||
is_stub: is_python_stub_file(path),
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
@@ -176,12 +174,13 @@ impl<'a> Checker<'a> {
|
||||
///
|
||||
/// If the current expression in the context is not an f-string, returns ``None``.
|
||||
pub(crate) fn f_string_quote_style(&self) -> Option<Quote> {
|
||||
if !self.semantic.in_f_string() {
|
||||
let model = &self.semantic;
|
||||
if !model.in_f_string() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the quote character used to start the containing f-string.
|
||||
let expr = self.semantic.current_expression()?;
|
||||
let expr = model.expr()?;
|
||||
let string_range = self.indexer.f_string_range(expr.start())?;
|
||||
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
||||
|
||||
@@ -201,7 +200,7 @@ impl<'a> Checker<'a> {
|
||||
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
|
||||
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
|
||||
parent
|
||||
.and_then(|stmt| self.semantic.statement_id(stmt))
|
||||
.and_then(|stmt| self.semantic.stmts.node_id(stmt))
|
||||
.map_or(IsolationLevel::default(), |node_id| {
|
||||
IsolationLevel::Group(node_id.into())
|
||||
})
|
||||
@@ -234,6 +233,11 @@ impl<'a> Checker<'a> {
|
||||
&self.semantic
|
||||
}
|
||||
|
||||
/// Return `true` if the current file is a stub file (`.pyi`).
|
||||
pub(crate) const fn is_stub(&self) -> bool {
|
||||
self.is_stub
|
||||
}
|
||||
|
||||
/// The [`Path`] to the file under analysis.
|
||||
pub(crate) const fn path(&self) -> &'a Path {
|
||||
self.path
|
||||
@@ -263,7 +267,7 @@ where
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_statement(stmt);
|
||||
self.semantic.push_stmt(stmt);
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
@@ -287,7 +291,7 @@ where
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
if !self.semantic.seen_import_boundary()
|
||||
&& !helpers::is_assignment_to_a_dunder(stmt)
|
||||
&& !helpers::in_nested_block(self.semantic.current_statements())
|
||||
&& !helpers::in_nested_block(self.semantic.parents())
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
@@ -321,11 +325,11 @@ where
|
||||
// Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be
|
||||
// "foo.bar".
|
||||
let name = alias.name.split('.').next().unwrap();
|
||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
|
||||
BindingFlags::EXTERNAL,
|
||||
);
|
||||
} else {
|
||||
@@ -342,11 +346,11 @@ where
|
||||
}
|
||||
|
||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::Import(Import { call_path }),
|
||||
BindingKind::Import(Import { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
}
|
||||
@@ -371,7 +375,7 @@ where
|
||||
);
|
||||
} else if &alias.name == "*" {
|
||||
self.semantic
|
||||
.current_scope_mut()
|
||||
.scope_mut()
|
||||
.add_star_import(StarImport { level, module });
|
||||
} else {
|
||||
let mut flags = BindingFlags::EXTERNAL;
|
||||
@@ -390,16 +394,12 @@ where
|
||||
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
|
||||
// and `qualified_name` would be "foo.bar".
|
||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||
|
||||
// Attempt to resolve any relative imports; but if we don't know the current
|
||||
// module path, or the relative import extends beyond the package root,
|
||||
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
||||
let call_path = collect_import_from_member(level, module, &alias.name)
|
||||
.into_boxed_slice();
|
||||
let qualified_name =
|
||||
helpers::format_import_from_member(level, module, &alias.name);
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::FromImport(FromImport { call_path }),
|
||||
BindingKind::FromImport(FromImport { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
}
|
||||
@@ -420,7 +420,7 @@ where
|
||||
BindingKind::Global,
|
||||
BindingFlags::GLOBAL,
|
||||
);
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(name, binding_id);
|
||||
}
|
||||
}
|
||||
@@ -443,7 +443,7 @@ where
|
||||
BindingKind::Nonlocal(scope_id),
|
||||
BindingFlags::NONLOCAL,
|
||||
);
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(name, binding_id);
|
||||
}
|
||||
}
|
||||
@@ -454,16 +454,22 @@ where
|
||||
|
||||
// Step 2: Traversal
|
||||
match stmt {
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_params,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_params,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body,
|
||||
parameters,
|
||||
decorator_list,
|
||||
type_params,
|
||||
returns,
|
||||
..
|
||||
}) => {
|
||||
// Visit the decorators and arguments, but avoid the body, which will be
|
||||
// deferred.
|
||||
for decorator in decorator_list {
|
||||
@@ -524,7 +530,8 @@ where
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
ExtractionTarget::Function(function_def),
|
||||
ExtractionTarget::Function,
|
||||
stmt,
|
||||
self.semantic.definition_id,
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
@@ -532,7 +539,8 @@ where
|
||||
|
||||
self.semantic.push_scope(match &stmt {
|
||||
Stmt::FunctionDef(stmt) => ScopeKind::Function(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef"),
|
||||
Stmt::AsyncFunctionDef(stmt) => ScopeKind::AsyncFunction(stmt),
|
||||
_ => unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef"),
|
||||
});
|
||||
|
||||
self.deferred.functions.push(self.semantic.snapshot());
|
||||
@@ -566,7 +574,8 @@ where
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
ExtractionTarget::Class(class_def),
|
||||
ExtractionTarget::Class,
|
||||
stmt,
|
||||
self.semantic.definition_id,
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
@@ -647,7 +656,7 @@ where
|
||||
// available at runtime.
|
||||
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
|
||||
let runtime_annotation = if self.semantic.future_annotations() {
|
||||
if self.semantic.current_scope().kind.is_class() {
|
||||
if self.semantic.scope().kind.is_class() {
|
||||
let baseclasses = &self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
@@ -666,7 +675,7 @@ where
|
||||
}
|
||||
} else {
|
||||
matches!(
|
||||
self.semantic.current_scope().kind,
|
||||
self.semantic.scope().kind,
|
||||
ScopeKind::Class(_) | ScopeKind::Module
|
||||
)
|
||||
};
|
||||
@@ -733,7 +742,8 @@ where
|
||||
|
||||
// Step 3: Clean-up
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.deferred.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
@@ -766,7 +776,7 @@ where
|
||||
analyze::statement(stmt, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_statement();
|
||||
self.semantic.pop_stmt();
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
||||
@@ -802,7 +812,7 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
self.semantic.push_expression(expr);
|
||||
self.semantic.push_expr(expr);
|
||||
|
||||
// Store the flags prior to any further descent, so that we can restore them after visiting
|
||||
// the node.
|
||||
@@ -830,7 +840,7 @@ where
|
||||
}) => {
|
||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||
if id == "locals" && ctx.is_load() {
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.set_uses_locals();
|
||||
}
|
||||
}
|
||||
@@ -1196,7 +1206,7 @@ where
|
||||
));
|
||||
}
|
||||
}
|
||||
Expr::FString(_) => {
|
||||
Expr::JoinedStr(_) => {
|
||||
self.semantic.flags |= SemanticModelFlags::F_STRING;
|
||||
visitor::walk_expr(self, expr);
|
||||
}
|
||||
@@ -1220,7 +1230,7 @@ where
|
||||
analyze::expression(expr, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_expression();
|
||||
self.semantic.pop_expr();
|
||||
}
|
||||
|
||||
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
||||
@@ -1275,7 +1285,7 @@ where
|
||||
|
||||
fn visit_format_spec(&mut self, format_spec: &'b Expr) {
|
||||
match format_spec {
|
||||
Expr::FString(ast::ExprFString { values, range: _ }) => {
|
||||
Expr::JoinedStr(ast::ExprJoinedStr { values, range: _ }) => {
|
||||
for value in values {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
@@ -1600,7 +1610,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
let parent = self.semantic.current_statement();
|
||||
let parent = self.semantic.stmt();
|
||||
|
||||
if matches!(
|
||||
parent,
|
||||
@@ -1615,7 +1625,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
if parent.is_for_stmt() {
|
||||
if matches!(parent, Stmt::For(_) | Stmt::AsyncFor(_)) {
|
||||
self.add_binding(
|
||||
id,
|
||||
expr.range(),
|
||||
@@ -1635,7 +1645,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope = self.semantic.current_scope();
|
||||
let scope = self.semantic.scope();
|
||||
|
||||
if scope.kind.is_module()
|
||||
&& match parent {
|
||||
@@ -1687,8 +1697,8 @@ impl<'a> Checker<'a> {
|
||||
|
||||
if self
|
||||
.semantic
|
||||
.current_expressions()
|
||||
.any(Expr::is_named_expr_expr)
|
||||
.expr_ancestors()
|
||||
.any(|expr| expr.is_named_expr_expr())
|
||||
{
|
||||
self.add_binding(
|
||||
id,
|
||||
@@ -1714,7 +1724,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.resolve_del(id, expr.range());
|
||||
|
||||
if helpers::on_conditional_branch(&mut self.semantic.current_statements()) {
|
||||
if helpers::on_conditional_branch(&mut self.semantic.parents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1722,7 +1732,7 @@ impl<'a> Checker<'a> {
|
||||
let binding_id =
|
||||
self.semantic
|
||||
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(id, binding_id);
|
||||
}
|
||||
|
||||
@@ -1776,7 +1786,7 @@ impl<'a> Checker<'a> {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
}
|
||||
}
|
||||
if self.source_type.is_stub() {
|
||||
if self.is_stub {
|
||||
if self.enabled(Rule::QuotedAnnotationInStub) {
|
||||
flake8_pyi::rules::quoted_annotation_in_stub(self, value, range);
|
||||
}
|
||||
@@ -1814,14 +1824,19 @@ impl<'a> Checker<'a> {
|
||||
for snapshot in deferred_functions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
}) = self.semantic.current_statement()
|
||||
{
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_body(body);
|
||||
} else {
|
||||
unreachable!("Expected Stmt::FunctionDef")
|
||||
match &self.semantic.stmt() {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body, parameters, ..
|
||||
}) => {
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_body(body);
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Expected Stmt::FunctionDef | Stmt::AsyncFunctionDef")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1913,7 +1928,6 @@ pub(crate) fn check_ast(
|
||||
noqa: flags::Noqa,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
) -> Vec<Diagnostic> {
|
||||
let module_path = package.and_then(|package| to_module_path(package, path));
|
||||
let module = Module {
|
||||
@@ -1941,7 +1955,6 @@ pub(crate) fn check_ast(
|
||||
stylist,
|
||||
indexer,
|
||||
Importer::new(python_ast, locator, stylist),
|
||||
source_type,
|
||||
);
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
@@ -10,7 +10,7 @@ use ruff_python_ast::imports::{ImportMap, ModuleImport};
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::directives::IsortDirectives;
|
||||
@@ -87,12 +87,12 @@ pub(crate) fn check_imports(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
let is_stub = is_python_stub_file(path);
|
||||
|
||||
// Extract all import blocks from the AST.
|
||||
let tracker = {
|
||||
let mut tracker =
|
||||
BlockBuilder::new(locator, directives, source_type.is_stub(), source_kind);
|
||||
let mut tracker = BlockBuilder::new(locator, directives, is_stub, source_kind);
|
||||
tracker.visit_body(python_ast);
|
||||
tracker
|
||||
};
|
||||
@@ -104,13 +104,7 @@ pub(crate) fn check_imports(
|
||||
for block in &blocks {
|
||||
if !block.imports.is_empty() {
|
||||
if let Some(diagnostic) = isort::rules::organize_imports(
|
||||
block,
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
settings,
|
||||
package,
|
||||
source_type,
|
||||
block, locator, stylist, indexer, settings, package,
|
||||
) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -119,11 +113,7 @@ pub(crate) fn check_imports(
|
||||
}
|
||||
if settings.rules.enabled(Rule::MissingRequiredImport) {
|
||||
diagnostics.extend(isort::rules::add_required_imports(
|
||||
python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
python_ast, locator, stylist, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -94,15 +94,8 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||
// suppressed.
|
||||
if settings.rules.enabled(Rule::UnusedNOQA)
|
||||
&& analyze_directives
|
||||
&& !exemption.is_some_and(|exemption| match exemption {
|
||||
FileExemption::All => true,
|
||||
FileExemption::Codes(codes) => codes.contains(&Rule::UnusedNOQA.noqa_code()),
|
||||
})
|
||||
{
|
||||
// Enforce that the noqa directive was actually used (RUF100).
|
||||
if analyze_directives && settings.rules.enabled(Rule::UnusedNOQA) {
|
||||
for line in noqa_directives.lines() {
|
||||
match &line.directive {
|
||||
Directive::All(directive) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Extract docstrings from an AST.
|
||||
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
||||
|
||||
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||
|
||||
/// Extract a docstring from a function or class body.
|
||||
@@ -27,48 +28,63 @@ pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||
match definition {
|
||||
Definition::Module(module) => docstring_from(module.python_ast),
|
||||
Definition::Member(member) => docstring_from(member.body()),
|
||||
Definition::Member(member) => {
|
||||
if let Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
||||
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) = &member.stmt
|
||||
{
|
||||
docstring_from(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ExtractionTarget<'a> {
|
||||
Class(&'a ast::StmtClassDef),
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
pub(crate) enum ExtractionTarget {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||
pub(crate) fn extract_definition<'a>(
|
||||
target: ExtractionTarget<'a>,
|
||||
target: ExtractionTarget,
|
||||
stmt: &'a Stmt,
|
||||
parent: DefinitionId,
|
||||
definitions: &Definitions<'a>,
|
||||
) -> Member<'a> {
|
||||
match target {
|
||||
ExtractionTarget::Function(function) => match &definitions[parent] {
|
||||
ExtractionTarget::Function => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Function(function),
|
||||
kind: MemberKind::Function,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(Member {
|
||||
kind: MemberKind::Class(_) | MemberKind::NestedClass(_),
|
||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
||||
..
|
||||
}) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Method(function),
|
||||
kind: MemberKind::Method,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(_) => Member {
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedFunction(function),
|
||||
kind: MemberKind::NestedFunction,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
ExtractionTarget::Class(class) => match &definitions[parent] {
|
||||
Definition::Module(_) => Member {
|
||||
ExtractionTarget::Class => match &definitions[parent] {
|
||||
Definition::Module(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::Class(class),
|
||||
kind: MemberKind::Class,
|
||||
stmt,
|
||||
},
|
||||
Definition::Member(_) => Member {
|
||||
Definition::Member(..) => Member {
|
||||
parent,
|
||||
kind: MemberKind::NestedClass(class),
|
||||
kind: MemberKind::NestedClass,
|
||||
stmt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Insert statements into Python code.
|
||||
use std::ops::Add;
|
||||
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -137,7 +137,6 @@ impl<'a> Insertion<'a> {
|
||||
mut location: TextSize,
|
||||
locator: &Locator<'a>,
|
||||
stylist: &Stylist,
|
||||
source_type: PySourceType,
|
||||
) -> Insertion<'a> {
|
||||
enum Awaiting {
|
||||
Colon(u32),
|
||||
@@ -147,7 +146,7 @@ impl<'a> Insertion<'a> {
|
||||
|
||||
let mut state = Awaiting::Colon(0);
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(locator.after(location), source_type.as_mode(), location).flatten()
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
|
||||
{
|
||||
match state {
|
||||
// Iterate until we find the colon indicating the start of the block body.
|
||||
@@ -301,20 +300,20 @@ fn match_leading_semicolon(s: &str) -> Option<TextSize> {
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{parse_suite, Mode};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use super::Insertion;
|
||||
|
||||
#[test]
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(Insertion::start_of_file(&program, &locator, &stylist))
|
||||
@@ -425,10 +424,10 @@ x = 1
|
||||
#[test]
|
||||
fn start_of_block() {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion {
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Insertion::start_of_block(offset, &locator, &stylist, PySourceType::default())
|
||||
Insertion::start_of_block(offset, &locator, &stylist)
|
||||
}
|
||||
|
||||
let contents = "if True: pass";
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{ImportAlias, Name, NameOrAttribute};
|
||||
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -87,13 +87,13 @@ impl<'a> Importer<'a> {
|
||||
/// import statement.
|
||||
pub(crate) fn runtime_import_edit(
|
||||
&self,
|
||||
import: &ImportedMembers,
|
||||
import: &StmtImports,
|
||||
at: TextSize,
|
||||
) -> Result<RuntimeImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
&import.qualified_names,
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
@@ -118,15 +118,14 @@ impl<'a> Importer<'a> {
|
||||
/// `TYPE_CHECKING` block.
|
||||
pub(crate) fn typing_import_edit(
|
||||
&self,
|
||||
import: &ImportedMembers,
|
||||
import: &StmtImports,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
source_type: PySourceType,
|
||||
) -> Result<TypingImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
&import.qualified_names,
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
@@ -141,7 +140,7 @@ impl<'a> Importer<'a> {
|
||||
// Add the import to a `TYPE_CHECKING` block.
|
||||
let add_import_edit = if let Some(block) = self.preceding_type_checking_block(at) {
|
||||
// Add the import to the `TYPE_CHECKING` block.
|
||||
self.add_to_type_checking_block(&content, block.start(), source_type)
|
||||
self.add_to_type_checking_block(&content, block.start())
|
||||
} else {
|
||||
// Add the import to a new `TYPE_CHECKING` block.
|
||||
self.add_type_checking_block(
|
||||
@@ -354,13 +353,8 @@ impl<'a> Importer<'a> {
|
||||
}
|
||||
|
||||
/// Add an import statement to an existing `TYPE_CHECKING` block.
|
||||
fn add_to_type_checking_block(
|
||||
&self,
|
||||
content: &str,
|
||||
at: TextSize,
|
||||
source_type: PySourceType,
|
||||
) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist, source_type).into_edit(content)
|
||||
fn add_to_type_checking_block(&self, content: &str, at: TextSize) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist).into_edit(content)
|
||||
}
|
||||
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
@@ -452,11 +446,11 @@ impl<'a> ImportRequest<'a> {
|
||||
}
|
||||
|
||||
/// An existing list of module or member imports, located within an import statement.
|
||||
pub(crate) struct ImportedMembers<'a> {
|
||||
pub(crate) struct StmtImports<'a> {
|
||||
/// The import statement.
|
||||
pub(crate) statement: &'a Stmt,
|
||||
/// The "names" of the imported members.
|
||||
pub(crate) names: Vec<&'a str>,
|
||||
pub(crate) stmt: &'a Stmt,
|
||||
/// The "qualified names" of the imported modules or members.
|
||||
pub(crate) qualified_names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
/// The result of an [`Importer::get_or_import_symbol`] call.
|
||||
|
||||
@@ -24,6 +24,8 @@ use crate::IOError;
|
||||
|
||||
pub const JUPYTER_NOTEBOOK_EXT: &str = "ipynb";
|
||||
|
||||
const MAGIC_PREFIX: [&str; 3] = ["%", "!", "?"];
|
||||
|
||||
/// Run round-trip source code generation on a given Jupyter notebook file path.
|
||||
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
let mut notebook = Notebook::read(path).map_err(|err| {
|
||||
@@ -76,21 +78,26 @@ impl Cell {
|
||||
/// Return `true` if it's a valid code cell.
|
||||
///
|
||||
/// A valid code cell is a cell where the cell type is [`Cell::Code`] and the
|
||||
/// source doesn't contain a cell magic.
|
||||
/// source doesn't contain a magic, shell or help command.
|
||||
fn is_valid_code_cell(&self) -> bool {
|
||||
let source = match self {
|
||||
Cell::Code(cell) => &cell.source,
|
||||
_ => return false,
|
||||
};
|
||||
// Ignore cells containing cell magic. This is different from line magic
|
||||
// which is allowed and ignored by the parser.
|
||||
// Ignore a cell if it contains a magic command. There could be valid
|
||||
// Python code as well, but we'll ignore that for now.
|
||||
// TODO(dhruvmanila): https://github.com/psf/black/blob/main/src/black/handle_ipynb_magics.py
|
||||
!match source {
|
||||
SourceValue::String(string) => string
|
||||
.lines()
|
||||
.any(|line| line.trim_start().starts_with("%%")),
|
||||
SourceValue::StringArray(string_array) => string_array
|
||||
.iter()
|
||||
.any(|line| line.trim_start().starts_with("%%")),
|
||||
SourceValue::String(string) => string.lines().any(|line| {
|
||||
MAGIC_PREFIX
|
||||
.iter()
|
||||
.any(|prefix| line.trim_start().starts_with(prefix))
|
||||
}),
|
||||
SourceValue::StringArray(string_array) => string_array.iter().any(|line| {
|
||||
MAGIC_PREFIX
|
||||
.iter()
|
||||
.any(|prefix| line.trim_start().starts_with(prefix))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,10 +513,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Path::new("markdown.json"), false; "markdown")]
|
||||
#[test_case(Path::new("only_magic.json"), true; "only_magic")]
|
||||
#[test_case(Path::new("code_and_magic.json"), true; "code_and_magic")]
|
||||
#[test_case(Path::new("only_magic.json"), false; "only_magic")]
|
||||
#[test_case(Path::new("code_and_magic.json"), false; "code_and_magic")]
|
||||
#[test_case(Path::new("only_code.json"), true; "only_code")]
|
||||
#[test_case(Path::new("cell_magic.json"), false; "cell_magic")]
|
||||
fn test_is_valid_code_cell(path: &Path, expected: bool) -> Result<()> {
|
||||
assert_eq!(read_jupyter_cell(path)?.is_valid_code_cell(), expected);
|
||||
Ok(())
|
||||
@@ -561,7 +567,7 @@ print("after empty cells")
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<()> {
|
||||
let path = "isort.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
let (diagnostics, source_kind) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("isort_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
@@ -570,34 +576,10 @@ print("after empty cells")
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_magics() -> Result<()> {
|
||||
let path = "line_magics.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("line_magics_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<()> {
|
||||
let path = "unused_variable.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("unused_variable_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let path = "before_fix.ipynb".to_string();
|
||||
let (_, _, source_kind) = test_notebook_path(
|
||||
let (_, source_kind) = test_notebook_path(
|
||||
path,
|
||||
Path::new("after_fix.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
|
||||
@@ -47,43 +47,4 @@ isort.ipynb:cell 2:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
7 9 | def foo():
|
||||
8 10 | pass
|
||||
|
||||
isort.ipynb:cell 3:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | / from pathlib import Path
|
||||
2 | | import sys
|
||||
3 | |
|
||||
4 | | %matplotlib \
|
||||
| |_^ I001
|
||||
5 | --inline
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
6 6 | # Newline should be added here
|
||||
7 7 | def foo():
|
||||
8 8 | pass
|
||||
9 |+import sys
|
||||
9 10 | from pathlib import Path
|
||||
10 |-import sys
|
||||
11 11 |
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
|
||||
isort.ipynb:cell 3:7:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
5 | --inline
|
||||
6 |
|
||||
7 | / import math
|
||||
8 | | import abc
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
14 14 |
|
||||
15 |+import abc
|
||||
15 16 | import math
|
||||
16 |-import abc
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
3 | %matplotlib inline
|
||||
4 |
|
||||
5 | import os
|
||||
| ^^ F401
|
||||
6 |
|
||||
7 | _ = math.pi
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | %matplotlib inline
|
||||
4 4 |
|
||||
5 |-import os
|
||||
6 5 |
|
||||
7 6 | _ = math.pi
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
unused_variable.ipynb:cell 1:2:5: F841 [*] Local variable `foo1` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | foo1 = %matplotlib --list
|
||||
| ^^^^ F841
|
||||
3 | foo2: list[str] = %matplotlib --list
|
||||
|
|
||||
= help: Remove assignment to unused variable `foo1`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | def f():
|
||||
2 |- foo1 = %matplotlib --list
|
||||
2 |+ %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 1:3:5: F841 [*] Local variable `foo2` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | foo1 = %matplotlib --list
|
||||
3 | foo2: list[str] = %matplotlib --list
|
||||
| ^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `foo2`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | def f():
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 |- foo2: list[str] = %matplotlib --list
|
||||
3 |+ %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 2:2:5: F841 [*] Local variable `bar1` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | bar1 = !pwd
|
||||
| ^^^^ F841
|
||||
3 | bar2: str = !pwd
|
||||
|
|
||||
= help: Remove assignment to unused variable `bar1`
|
||||
|
||||
ℹ Suggested fix
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 |- bar1 = !pwd
|
||||
5 |+ !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 2:3:5: F841 [*] Local variable `bar2` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | bar1 = !pwd
|
||||
3 | bar2: str = !pwd
|
||||
| ^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `bar2`
|
||||
|
||||
ℹ Suggested fix
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 |- bar2: str = !pwd
|
||||
6 |+ !pwd
|
||||
|
||||
|
||||
@@ -7,15 +7,14 @@ use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use ruff_python_parser::ParseError;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::{Locator, SourceFileBuilder};
|
||||
|
||||
use crate::autofix::{fix_file, FixResult};
|
||||
@@ -82,7 +81,6 @@ pub fn check_path(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
@@ -103,13 +101,9 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_tokens())
|
||||
{
|
||||
let is_stub = is_python_stub_file(path);
|
||||
diagnostics.extend(check_tokens(
|
||||
&tokens,
|
||||
path,
|
||||
locator,
|
||||
indexer,
|
||||
settings,
|
||||
source_type.is_stub(),
|
||||
&tokens, path, locator, indexer, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -144,11 +138,7 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_imports());
|
||||
if use_ast || use_imports || use_doc_lines {
|
||||
match ruff_python_parser::parse_program_tokens(
|
||||
tokens,
|
||||
&path.to_string_lossy(),
|
||||
source_type.is_jupyter(),
|
||||
) {
|
||||
match ruff_python_parser::parse_program_tokens(tokens, &path.to_string_lossy()) {
|
||||
Ok(python_ast) => {
|
||||
if use_ast {
|
||||
diagnostics.extend(check_ast(
|
||||
@@ -161,7 +151,6 @@ pub fn check_path(
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
source_type,
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
@@ -175,7 +164,6 @@ pub fn check_path(
|
||||
path,
|
||||
package,
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
imports = module_imports;
|
||||
diagnostics.extend(import_diagnostics);
|
||||
@@ -268,13 +256,11 @@ const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
||||
pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings) -> Result<usize> {
|
||||
let source_type = PySourceType::from(path);
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&contents);
|
||||
@@ -308,7 +294,6 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
settings,
|
||||
flags::Noqa::Disabled,
|
||||
None,
|
||||
source_type,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
@@ -341,10 +326,9 @@ pub fn lint_only(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
@@ -375,7 +359,6 @@ pub fn lint_only(
|
||||
settings,
|
||||
noqa,
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
|
||||
result.map(|(diagnostics, imports)| {
|
||||
@@ -422,7 +405,6 @@ pub fn lint_fix<'a>(
|
||||
noqa: flags::Noqa,
|
||||
settings: &Settings,
|
||||
source_kind: &mut SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> Result<FixerResult<'a>> {
|
||||
let mut transformed = Cow::Borrowed(contents);
|
||||
|
||||
@@ -438,8 +420,7 @@ pub fn lint_fix<'a>(
|
||||
// Continuously autofix until the source code stabilizes.
|
||||
loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(&transformed, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&transformed);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&transformed);
|
||||
@@ -470,7 +451,6 @@ pub fn lint_fix<'a>(
|
||||
settings,
|
||||
noqa,
|
||||
Some(source_kind),
|
||||
source_type,
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
|
||||
@@ -231,7 +231,7 @@ impl Renamer {
|
||||
}
|
||||
BindingKind::SubmoduleImport(import) => {
|
||||
// Ex) Rename `import pandas.core` to `import pandas as pd`.
|
||||
let module_name = import.call_path.first().unwrap();
|
||||
let module_name = import.qualified_name.split('.').next().unwrap();
|
||||
Some(Edit::range_replacement(
|
||||
format!("{module_name} as {target}"),
|
||||
binding.range,
|
||||
|
||||
@@ -262,6 +262,7 @@ pub fn python_files_in_path(
|
||||
builder.add(path);
|
||||
}
|
||||
builder.standard_filters(pyproject_config.settings.lib.respect_gitignore);
|
||||
builder.require_git(false);
|
||||
builder.hidden(false);
|
||||
let walker = builder.build_parallel();
|
||||
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_python_ast::{PySourceType, Ranged};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
/// ANN204
|
||||
pub(crate) fn add_return_annotation<T: Ranged>(
|
||||
statement: &T,
|
||||
annotation: &str,
|
||||
source_type: PySourceType,
|
||||
pub(crate) fn add_return_annotation(
|
||||
locator: &Locator,
|
||||
stmt: &Stmt,
|
||||
annotation: &str,
|
||||
) -> Result<Edit> {
|
||||
let contents = &locator.contents()[statement.range()];
|
||||
let contents = &locator.contents()[stmt.range()];
|
||||
|
||||
// Find the colon (following the `def` keyword).
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(contents, source_type.as_mode(), statement.start()).flatten()
|
||||
{
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, stmt.start()).flatten() {
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
return Ok(Edit::insertion(format!(" -> {annotation}"), range.start()));
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Parameters, Stmt};
|
||||
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Definition, SemanticModel};
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
||||
|
||||
pub(super) fn match_function_def(
|
||||
stmt: &Stmt,
|
||||
) -> (&str, &Parameters, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
name,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
}) => (
|
||||
name,
|
||||
parameters,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
body,
|
||||
decorator_list,
|
||||
),
|
||||
_ => panic!("Found non-FunctionDef in match_function_def"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||
let function = definition.as_function_def()?;
|
||||
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||
Some(function.name.to_string())
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
Some(name.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -18,12 +60,19 @@ pub(crate) fn is_overload_impl(
|
||||
overloaded_name: &str,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
let Some(function) = definition.as_function_def() else {
|
||||
return false;
|
||||
};
|
||||
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||
false
|
||||
if let Definition::Member(Member {
|
||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
{
|
||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
||||
false
|
||||
} else {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
name == overloaded_name
|
||||
}
|
||||
} else {
|
||||
function.name.as_str() == overloaded_name
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||
use ruff_python_parser::typing::parse_type_annotation;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::Definition;
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::flake8_annotations::fixes;
|
||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||
|
||||
use super::super::fixes;
|
||||
use super::super::helpers::match_function_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that function arguments have type annotations.
|
||||
///
|
||||
@@ -494,23 +498,20 @@ pub(crate) fn definition(
|
||||
definition: &Definition,
|
||||
visibility: visibility::Visibility,
|
||||
) -> Vec<Diagnostic> {
|
||||
let Some(function) = definition.as_function_def() else {
|
||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||
// We could adhere more closely to `flake8-annotations` by defining public
|
||||
// vs. secret vs. protected.
|
||||
let Definition::Member(Member { kind, stmt, .. }) = definition else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let ast::StmtFunctionDef {
|
||||
range: _,
|
||||
is_async: _,
|
||||
decorator_list,
|
||||
name,
|
||||
type_params: _,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
} = function;
|
||||
|
||||
let is_method = definition.is_method();
|
||||
let is_method = match kind {
|
||||
MemberKind::Method => true,
|
||||
MemberKind::Function | MemberKind::NestedFunction => false,
|
||||
_ => return vec![],
|
||||
};
|
||||
|
||||
let (name, arguments, returns, body, decorator_list) = match_function_def(stmt);
|
||||
// Keep track of whether we've seen any typed arguments or return values.
|
||||
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||
let mut has_typed_return = false; // Return value has been typed?
|
||||
@@ -527,19 +528,20 @@ pub(crate) fn definition(
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in parameters
|
||||
} in arguments
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(
|
||||
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
|
||||
is_method
|
||||
&& !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()),
|
||||
),
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed parameters
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = ¶meter.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
@@ -570,7 +572,7 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN002, ANN401
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
@@ -596,7 +598,7 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN003, ANN401
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
@@ -627,18 +629,18 @@ pub(crate) fn definition(
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
if is_method && !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if let Some(ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
}) = parameters
|
||||
}) = arguments
|
||||
.posonlyargs
|
||||
.first()
|
||||
.or_else(|| parameters.args.first())
|
||||
.or_else(|| arguments.args.first())
|
||||
{
|
||||
if parameter.annotation.is_none() {
|
||||
if visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingTypeCls) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeCls {
|
||||
@@ -674,22 +676,24 @@ pub(crate) fn definition(
|
||||
// (explicitly or implicitly).
|
||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||
) {
|
||||
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||
if is_method && visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeClassMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||
} else if is_method
|
||||
&& visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic())
|
||||
{
|
||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingReturnTypeStaticMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
} else if is_method && visibility::is_init(name) {
|
||||
@@ -701,17 +705,12 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
function,
|
||||
"None",
|
||||
checker.source_type,
|
||||
checker.locator(),
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
fixes::add_return_annotation(checker.locator(), stmt, "None")
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -723,18 +722,13 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeSpecialMethod {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(return_type) = simple_magic_return_type(name) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
function,
|
||||
return_type,
|
||||
checker.source_type,
|
||||
checker.locator(),
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
fixes::add_return_annotation(checker.locator(), stmt, return_type)
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -748,7 +742,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -758,7 +752,7 @@ pub(crate) fn definition(
|
||||
MissingReturnTypePrivateFunction {
|
||||
name: name.to_string(),
|
||||
},
|
||||
function.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,21 +112,21 @@ fn py_stat(call_path: &CallPath) -> Option<u16> {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_value(expr: &Expr, semantic: &SemanticModel) -> Option<u16> {
|
||||
fn int_value(expr: &Expr, model: &SemanticModel) -> Option<u16> {
|
||||
match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) => value.to_u16(),
|
||||
Expr::Attribute(_) => semantic.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
Expr::Attribute(_) => model.resolve_call_path(expr).as_ref().and_then(py_stat),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op,
|
||||
right,
|
||||
range: _,
|
||||
}) => {
|
||||
let left_value = int_value(left, semantic)?;
|
||||
let right_value = int_value(right, semantic)?;
|
||||
let left_value = int_value(left, model)?;
|
||||
let right_value = int_value(right, model)?;
|
||||
match op {
|
||||
Operator::BitAnd => Some(left_value & right_value),
|
||||
Operator::BitOr => Some(left_value | right_value),
|
||||
|
||||
@@ -53,7 +53,7 @@ fn matches_sql_statement(string: &str) -> bool {
|
||||
SQL_REGEX.is_match(string)
|
||||
}
|
||||
|
||||
fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
match expr {
|
||||
// "select * from table where val = " + "str" + ...
|
||||
// "select * from table where val = %s" % ...
|
||||
@@ -62,8 +62,8 @@ fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bo
|
||||
..
|
||||
}) => {
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
if semantic
|
||||
.current_expression_parent()
|
||||
if model
|
||||
.expr_parent()
|
||||
.map_or(true, |parent| !parent.is_bin_op_expr())
|
||||
{
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
@@ -80,7 +80,7 @@ fn matches_string_format_expression(expr: &Expr, semantic: &SemanticModel) -> bo
|
||||
attr == "format" && string_literal(value).is_some()
|
||||
}
|
||||
// f"select * from table where val = {val}"
|
||||
Expr::FString(_) => true,
|
||||
Expr::JoinedStr(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,31 +10,6 @@ use crate::{
|
||||
checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Check for method calls that initiate a subprocess with a shell.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Starting a subprocess with a shell can allow attackers to execute arbitrary
|
||||
/// shell commands. Consider starting the process without a shell call and
|
||||
/// sanitize the input to mitigate the risk of shell injection.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.run("ls -l", shell=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.run(["ls", "-l"])
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct SubprocessPopenWithShellEqualsTrue {
|
||||
seems_safe: bool,
|
||||
@@ -53,30 +28,6 @@ impl Violation for SubprocessPopenWithShellEqualsTrue {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Check for method calls that initiate a subprocess without a shell.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Starting a subprocess without a shell can prevent attackers from executing
|
||||
/// arbitrary shell commands; however, it is still error-prone. Consider
|
||||
/// validating the input.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Prone to false positives as it is difficult to determine whether the
|
||||
/// passed arguments have been validated ([#4045]).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// cmd = input("Enter a command: ").split()
|
||||
/// subprocess.run(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||
///
|
||||
/// [#4045]: https://github.com/astral-sh/ruff/issues/4045
|
||||
#[violation]
|
||||
pub struct SubprocessWithoutShellEqualsTrue;
|
||||
|
||||
|
||||
@@ -159,12 +159,18 @@ pub(crate) fn abstract_base_class(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
let (Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
}) = stmt
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
})) = stmt
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
|
||||
/// B019
|
||||
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
for decorator in decorator_list {
|
||||
|
||||
@@ -150,9 +150,7 @@ fn duplicate_handler_exceptions<'a>(
|
||||
if unique_elts.len() == 1 {
|
||||
checker.generator().expr(unique_elts[0])
|
||||
} else {
|
||||
// Multiple exceptions must always be parenthesized. This is done
|
||||
// manually as the generator never parenthesizes lone tuples.
|
||||
format!("({})", checker.generator().expr(&type_pattern(unique_elts)))
|
||||
checker.generator().expr(&type_pattern(unique_elts))
|
||||
},
|
||||
expr.range(),
|
||||
)));
|
||||
|
||||
@@ -50,7 +50,7 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
return;
|
||||
};
|
||||
if !value.is_f_string_expr() {
|
||||
if !value.is_joined_str_expr() {
|
||||
return;
|
||||
}
|
||||
checker
|
||||
|
||||
@@ -67,9 +67,9 @@ struct ArgumentDefaultVisitor<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ArgumentDefaultVisitor<'a> {
|
||||
fn new(semantic: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
||||
fn new(model: &'a SemanticModel<'a>, extend_immutable_calls: Vec<CallPath<'a>>) -> Self {
|
||||
Self {
|
||||
semantic,
|
||||
semantic: model,
|
||||
extend_immutable_calls,
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
parameters, body, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
parameters, body, ..
|
||||
}) => {
|
||||
// Collect all loaded variable names.
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
@@ -233,7 +236,7 @@ struct AssignedNamesVisitor<'a> {
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
if stmt.is_function_def_stmt() {
|
||||
if matches!(stmt, Stmt::FunctionDef(_) | Stmt::AsyncFunctionDef(_)) {
|
||||
// Don't recurse.
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +251,8 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, .. })
|
||||
| Stmt::AnnAssign(ast::StmtAnnAssign { target, .. })
|
||||
| Stmt::For(ast::StmtFor { target, .. }) => {
|
||||
| Stmt::For(ast::StmtFor { target, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { target, .. }) => {
|
||||
let mut visitor = NamesFromAssignmentsVisitor::default();
|
||||
visitor.visit_expr(target);
|
||||
self.names.extend(visitor.names);
|
||||
|
||||
@@ -69,13 +69,16 @@ fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
|
||||
));
|
||||
}
|
||||
match stmt {
|
||||
Stmt::While(ast::StmtWhile { body, .. }) | Stmt::For(ast::StmtFor { body, .. }) => {
|
||||
Stmt::While(ast::StmtWhile { body, .. })
|
||||
| Stmt::For(ast::StmtFor { body, .. })
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor { body, .. }) => {
|
||||
walk_stmt(checker, body, Stmt::is_return_stmt);
|
||||
}
|
||||
Stmt::If(ast::StmtIf { body, .. })
|
||||
| Stmt::Try(ast::StmtTry { body, .. })
|
||||
| Stmt::TryStar(ast::StmtTryStar { body, .. })
|
||||
| Stmt::With(ast::StmtWith { body, .. }) => {
|
||||
| Stmt::With(ast::StmtWith { body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { body, .. }) => {
|
||||
walk_stmt(checker, body, f);
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn setattr_with_constant(
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value: child,
|
||||
range: _,
|
||||
}) = checker.semantic().current_statement()
|
||||
}) = checker.semantic().stmt()
|
||||
{
|
||||
if expr == child.as_ref() {
|
||||
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
|
||||
|
||||
@@ -159,7 +159,7 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
|
||||
if certainty.into() {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().current_scope();
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn useless_expression(checker: &mut Checker, value: &Expr) {
|
||||
// Ignore strings, to avoid false positives with docstrings.
|
||||
if matches!(
|
||||
value,
|
||||
Expr::FString(_)
|
||||
Expr::JoinedStr(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Ellipsis,
|
||||
..
|
||||
|
||||
@@ -81,43 +81,33 @@ B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaul
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:258:27: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:254:27: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
| ^^ B006
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
|
||||
B006_B008.py:259:35: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:255:35: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^ B006
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
257 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:260:62: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:256:62: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
262 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:261:80: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
262 | ):
|
||||
263 | pass
|
||||
257 | ):
|
||||
258 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -64,22 +64,4 @@ B014.py:49:8: B014 [*] Exception handler with duplicate exception: `re.error`
|
||||
51 51 | pass
|
||||
52 52 |
|
||||
|
||||
B014.py:82:8: B014 [*] Exception handler with duplicate exception: `ValueError`
|
||||
|
|
||||
80 | try:
|
||||
81 | pass
|
||||
82 | except (ValueError, ValueError, TypeError):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B014
|
||||
83 | pass
|
||||
|
|
||||
= help: De-duplicate exceptions
|
||||
|
||||
ℹ Fix
|
||||
79 79 | # https://github.com/astral-sh/ruff/issues/6412
|
||||
80 80 | try:
|
||||
81 81 | pass
|
||||
82 |-except (ValueError, ValueError, TypeError):
|
||||
82 |+except (ValueError, TypeError):
|
||||
83 83 | pass
|
||||
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ pub(crate) fn builtin_method_shadowing(
|
||||
fn is_standard_library_override(
|
||||
name: &str,
|
||||
class_def: &ast::StmtClassDef,
|
||||
semantic: &SemanticModel,
|
||||
model: &SemanticModel,
|
||||
) -> bool {
|
||||
let Some(Arguments { args: bases, .. }) = class_def.arguments.as_deref() else {
|
||||
return false;
|
||||
@@ -139,13 +139,13 @@ fn is_standard_library_override(
|
||||
match name {
|
||||
// Ex) `Event#set`
|
||||
"set" => bases.iter().any(|base| {
|
||||
semantic
|
||||
model
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["threading", "Event"]))
|
||||
}),
|
||||
// Ex) `Filter#filter`
|
||||
"filter" => bases.iter().any(|base| {
|
||||
semantic
|
||||
model
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Filter"]))
|
||||
}),
|
||||
|
||||
@@ -7,7 +7,7 @@ use libcst_native::{
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
|
||||
TrailingWhitespace, Tuple,
|
||||
};
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
@@ -28,7 +28,7 @@ use crate::{
|
||||
pub(crate) fn fix_unnecessary_generator_list(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -58,7 +58,10 @@ pub(crate) fn fix_unnecessary_generator_list(
|
||||
}
|
||||
|
||||
/// (C401) Convert `set(x for x in y)` to `{x for x in y}`.
|
||||
pub(crate) fn fix_unnecessary_generator_set(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_generator_set(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -93,7 +96,10 @@ pub(crate) fn fix_unnecessary_generator_set(checker: &Checker, expr: &Expr) -> R
|
||||
|
||||
/// (C402) Convert `dict((x, x) for x in range(3))` to `{x: x for x in
|
||||
/// range(3)}`.
|
||||
pub(crate) fn fix_unnecessary_generator_dict(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_generator_dict(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -135,7 +141,7 @@ pub(crate) fn fix_unnecessary_generator_dict(checker: &Checker, expr: &Expr) ->
|
||||
/// (C403) Convert `set([x for x in y])` to `{x for x in y}`.
|
||||
pub(crate) fn fix_unnecessary_list_comprehension_set(
|
||||
checker: &Checker,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
@@ -171,7 +177,7 @@ pub(crate) fn fix_unnecessary_list_comprehension_set(
|
||||
/// range(3)}`.
|
||||
pub(crate) fn fix_unnecessary_list_comprehension_dict(
|
||||
checker: &Checker,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
@@ -256,7 +262,10 @@ fn drop_trailing_comma<'a>(
|
||||
}
|
||||
|
||||
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
|
||||
pub(crate) fn fix_unnecessary_literal_set(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_literal_set(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -297,7 +306,10 @@ pub(crate) fn fix_unnecessary_literal_set(checker: &Checker, expr: &Expr) -> Res
|
||||
}
|
||||
|
||||
/// (C406) Convert `dict([(1, 2)])` to `{1: 2}`.
|
||||
pub(crate) fn fix_unnecessary_literal_dict(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_literal_dict(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -360,7 +372,10 @@ pub(crate) fn fix_unnecessary_literal_dict(checker: &Checker, expr: &Expr) -> Re
|
||||
}
|
||||
|
||||
/// (C408)
|
||||
pub(crate) fn fix_unnecessary_collection_call(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_collection_call(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
enum Collection {
|
||||
Tuple,
|
||||
List,
|
||||
@@ -520,7 +535,7 @@ fn pad_expression(content: String, range: TextRange, checker: &Checker) -> Strin
|
||||
pub(crate) fn fix_unnecessary_literal_within_tuple_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -570,7 +585,7 @@ pub(crate) fn fix_unnecessary_literal_within_tuple_call(
|
||||
pub(crate) fn fix_unnecessary_literal_within_list_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -622,7 +637,7 @@ pub(crate) fn fix_unnecessary_literal_within_list_call(
|
||||
pub(crate) fn fix_unnecessary_list_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -644,7 +659,7 @@ pub(crate) fn fix_unnecessary_list_call(
|
||||
pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -756,7 +771,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -786,7 +801,7 @@ pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
pub(crate) fn fix_unnecessary_comprehension(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -873,8 +888,8 @@ pub(crate) fn fix_unnecessary_comprehension(
|
||||
pub(crate) fn fix_unnecessary_map(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
parent: Option<&Expr>,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
parent: Option<&ruff_python_ast::Expr>,
|
||||
object_type: ObjectType,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -1003,7 +1018,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
// If the expression is embedded in an f-string, surround it with spaces to avoid
|
||||
// syntax errors.
|
||||
if matches!(object_type, ObjectType::Set | ObjectType::Dict) {
|
||||
if parent.is_some_and(Expr::is_formatted_value_expr) {
|
||||
if parent.is_some_and(ruff_python_ast::Expr::is_formatted_value_expr) {
|
||||
content = format!(" {content} ");
|
||||
}
|
||||
}
|
||||
@@ -1018,7 +1033,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
pub(crate) fn fix_unnecessary_literal_within_dict_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -1037,7 +1052,7 @@ pub(crate) fn fix_unnecessary_literal_within_dict_call(
|
||||
pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(ListComp) -> Expr(GeneratorExp)
|
||||
let module_text = locator.slice(expr.range());
|
||||
|
||||
@@ -43,8 +43,8 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
|
||||
};
|
||||
|
||||
let (Some(grandparent), Some(parent)) = (
|
||||
checker.semantic().current_expression_grandparent(),
|
||||
checker.semantic().current_expression_parent(),
|
||||
checker.semantic().expr_grandparent(),
|
||||
checker.semantic().expr_parent(),
|
||||
) else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
CallDatetimeStrptimeWithoutZone,
|
||||
|
||||
@@ -18,8 +18,9 @@ use super::helpers;
|
||||
/// `datetime` objects are preferred, as they represent a specific moment in
|
||||
/// time, unlike "naive" objects.
|
||||
///
|
||||
/// `datetime.datetime.today()` creates a "naive" object; instead, use
|
||||
/// `datetime.datetime.now(tz=)` to create a timezone-aware object.
|
||||
/// `datetime.datetime.today()` crates a "naive" object; instead, use
|
||||
/// instead, use `datetime.datetime.now(tz=)` to create a timezone-aware
|
||||
/// object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -8,43 +8,6 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.utcfromtimestamp()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.utcfromtimestamp()` returns a naive datetime object;
|
||||
/// instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a
|
||||
/// timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.utcfromtimestamp()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeUtcfromtimestamp;
|
||||
|
||||
@@ -58,6 +21,15 @@ impl Violation for CallDatetimeUtcfromtimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `datetime.datetime.utcfromtimestamp()`. (DTZ004)
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
///
|
||||
/// Because naive `datetime` objects are treated by many `datetime` methods as
|
||||
/// local times, it is preferred to use aware datetimes to represent times in
|
||||
/// UTC. As such, the recommended way to create an object representing a
|
||||
/// specific timestamp in UTC is by calling `datetime.fromtimestamp(timestamp,
|
||||
/// tz=timezone.utc)`.
|
||||
pub(crate) fn call_datetime_utcfromtimestamp(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
|
||||
@@ -8,42 +8,6 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.utcnow()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.utcnow()` returns a naive datetime object; instead, use
|
||||
/// `datetime.datetime.now(tz=)` to return a timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.utcnow()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeUtcnow;
|
||||
|
||||
@@ -57,6 +21,14 @@ impl Violation for CallDatetimeUtcnow {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `datetime.datetime.today()`. (DTZ003)
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
///
|
||||
/// Because naive `datetime` objects are treated by many `datetime` methods as
|
||||
/// local times, it is preferred to use aware datetimes to represent times in
|
||||
/// UTC. As such, the recommended way to create an object representing the
|
||||
/// current time in UTC is by calling `datetime.now(timezone.utc)`.
|
||||
pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: TextRange) {
|
||||
if !checker
|
||||
.semantic()
|
||||
|
||||
@@ -31,13 +31,6 @@ use super::helpers;
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
#[violation]
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
|
||||
/// Check if the parent expression is a call to `astimezone`. This assumes that
|
||||
/// the current expression is a `datetime.datetime` object.
|
||||
pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool {
|
||||
checker.semantic().current_expression_parent().is_some_and( |parent| {
|
||||
checker.semantic().expr_parent().is_some_and( |parent| {
|
||||
matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr
|
||||
}
|
||||
}
|
||||
// Check for f-strings.
|
||||
Expr::FString(_) => {
|
||||
Expr::JoinedStr(_) => {
|
||||
if checker.enabled(Rule::FStringInException) {
|
||||
let mut diagnostic = Diagnostic::new(FStringInException, first.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Violation for FStringInGetTextFuncCall {
|
||||
/// INT001
|
||||
pub(crate) fn f_string_in_gettext_func_call(checker: &mut Checker, args: &[Expr]) {
|
||||
if let Some(first) = args.first() {
|
||||
if first.is_f_string_expr() {
|
||||
if first.is_joined_str_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FStringInGetTextFuncCall {}, first.range()));
|
||||
|
||||
@@ -50,14 +50,14 @@ pub(crate) fn explicit(expr: &Expr, locator: &Locator) -> Option<Diagnostic> {
|
||||
if matches!(op, Operator::Add) {
|
||||
if matches!(
|
||||
left.as_ref(),
|
||||
Expr::FString(_)
|
||||
Expr::JoinedStr(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Bytes(..),
|
||||
..
|
||||
})
|
||||
) && matches!(
|
||||
right.as_ref(),
|
||||
Expr::FString(_)
|
||||
Expr::JoinedStr(_)
|
||||
| Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(..) | Constant::Bytes(..),
|
||||
..
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::{Binding, Imported};
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -56,13 +56,11 @@ pub(crate) fn unconventional_import_alias(
|
||||
binding: &Binding,
|
||||
conventions: &FxHashMap<String, String>,
|
||||
) -> Option<Diagnostic> {
|
||||
let Some(import) = binding.as_any_import() else {
|
||||
let Some(qualified_name) = binding.qualified_name() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let qualified_name = import.qualified_name();
|
||||
|
||||
let Some(expected_alias) = conventions.get(qualified_name.as_str()) else {
|
||||
let Some(expected_alias) = conventions.get(qualified_name) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -73,7 +71,7 @@ pub(crate) fn unconventional_import_alias(
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnconventionalImportAlias {
|
||||
name: qualified_name,
|
||||
name: qualified_name.to_string(),
|
||||
asname: expected_alias.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
|
||||
@@ -62,7 +62,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
|
||||
_ => {}
|
||||
},
|
||||
// Check for f-strings.
|
||||
Expr::FString(_) => {
|
||||
Expr::JoinedStr(_) => {
|
||||
if checker.enabled(Rule::LoggingFString) {
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -15,15 +15,6 @@ G004.py:5:27: G004 Logging statement uses f-string
|
||||
4 | logging.info(f"Hello {name}")
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^ G004
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
|
||||
G004.py:8:14: G004 Logging statement uses f-string
|
||||
|
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^ G004
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ pub(crate) fn any_eq_ne_annotation(checker: &mut Checker, name: &str, parameters
|
||||
return;
|
||||
};
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::{Decorator, Expr, Parameters, Ranged, TypeParam, TypeParams};
|
||||
use ruff_python_ast::{
|
||||
Decorator, Expr, ParameterWithDefault, Parameters, Ranged, TypeParam, TypeParams,
|
||||
};
|
||||
use ruff_python_semantic::analyze::visibility::{
|
||||
is_abstract, is_classmethod, is_new, is_overload, is_staticmethod,
|
||||
};
|
||||
@@ -75,23 +77,15 @@ pub(crate) fn custom_type_var_return_type(
|
||||
args: &Parameters,
|
||||
type_params: Option<&TypeParams>,
|
||||
) {
|
||||
// Given, e.g., `def foo(self: _S, arg: bytes) -> _T`, extract `_T`.
|
||||
let Some(return_annotation) = returns else {
|
||||
if args.args.is_empty() && args.posonlyargs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(returns) = returns else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Given, e.g., `def foo(self: _S, arg: bytes)`, extract `_S`.
|
||||
let Some(self_or_cls_annotation) = args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.next()
|
||||
.and_then(|parameter_with_default| parameter_with_default.parameter.annotation.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -103,12 +97,14 @@ pub(crate) fn custom_type_var_return_type(
|
||||
return;
|
||||
}
|
||||
|
||||
let returns = map_subscript(returns);
|
||||
|
||||
let uses_custom_var: bool =
|
||||
if is_classmethod(decorator_list, checker.semantic()) || is_new(name) {
|
||||
class_method(self_or_cls_annotation, return_annotation, type_params)
|
||||
class_method(args, returns, type_params)
|
||||
} else {
|
||||
// If not static, or a class method or __new__ we know it is an instance method
|
||||
instance_method(self_or_cls_annotation, return_annotation, type_params)
|
||||
instance_method(args, returns, type_params)
|
||||
};
|
||||
|
||||
if uses_custom_var {
|
||||
@@ -116,7 +112,7 @@ pub(crate) fn custom_type_var_return_type(
|
||||
CustomTypeVarReturnType {
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
return_annotation.range(),
|
||||
returns.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -124,11 +120,17 @@ pub(crate) fn custom_type_var_return_type(
|
||||
/// Returns `true` if the class method is annotated with a custom `TypeVar` that is likely
|
||||
/// private.
|
||||
fn class_method(
|
||||
cls_annotation: &Expr,
|
||||
args: &Parameters,
|
||||
return_annotation: &Expr,
|
||||
type_params: Option<&TypeParams>,
|
||||
) -> bool {
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, value, .. }) = cls_annotation else {
|
||||
let ParameterWithDefault { parameter, .. } = &args.args[0];
|
||||
|
||||
let Some(annotation) = ¶meter.annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, value, .. }) = annotation.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -146,7 +148,7 @@ fn class_method(
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(return_annotation) = map_subscript(return_annotation) else {
|
||||
let Expr::Name(return_annotation) = return_annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -160,20 +162,26 @@ fn class_method(
|
||||
/// Returns `true` if the instance method is annotated with a custom `TypeVar` that is likely
|
||||
/// private.
|
||||
fn instance_method(
|
||||
self_annotation: &Expr,
|
||||
args: &Parameters,
|
||||
return_annotation: &Expr,
|
||||
type_params: Option<&TypeParams>,
|
||||
) -> bool {
|
||||
let ParameterWithDefault { parameter, .. } = &args.args[0];
|
||||
|
||||
let Some(annotation) = ¶meter.annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: first_arg_type, ..
|
||||
}) = self_annotation
|
||||
}) = annotation.as_ref()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: return_type, ..
|
||||
}) = map_subscript(return_annotation)
|
||||
}) = return_annotation
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -242,11 +242,11 @@ fn check_positional_args(
|
||||
/// Return the non-`None` annotation element of a PEP 604-style union or `Optional` annotation.
|
||||
fn non_none_annotation_element<'a>(
|
||||
annotation: &'a Expr,
|
||||
semantic: &SemanticModel,
|
||||
model: &SemanticModel,
|
||||
) -> Option<&'a Expr> {
|
||||
// E.g., `typing.Union` or `typing.Optional`
|
||||
if let Expr::Subscript(ExprSubscript { value, slice, .. }) = annotation {
|
||||
if semantic.match_typing_expr(value, "Optional") {
|
||||
if model.match_typing_expr(value, "Optional") {
|
||||
return if is_const_none(slice) {
|
||||
None
|
||||
} else {
|
||||
@@ -254,7 +254,7 @@ fn non_none_annotation_element<'a>(
|
||||
};
|
||||
}
|
||||
|
||||
if !semantic.match_typing_expr(value, "Union") {
|
||||
if !model.match_typing_expr(value, "Union") {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -297,8 +297,8 @@ fn non_none_annotation_element<'a>(
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is the `object` builtin or the `_typeshed.Unused` type.
|
||||
fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
fn is_object_or_unused(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.is_some_and(|call_path| {
|
||||
@@ -310,34 +310,34 @@ fn is_object_or_unused(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is `BaseException`.
|
||||
fn is_base_exception(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
fn is_base_exception(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "BaseException"]))
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is the `types.TracebackType` type.
|
||||
fn is_traceback_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
fn is_traceback_type(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
model
|
||||
.resolve_call_path(expr)
|
||||
.as_ref()
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["types", "TracebackType"]))
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is, e.g., `Type[BaseException]`.
|
||||
fn is_base_exception_type(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn is_base_exception_type(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
let Expr::Subscript(ExprSubscript { value, slice, .. }) = expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if semantic.match_typing_expr(value, "Type")
|
||||
|| semantic
|
||||
if model.match_typing_expr(value, "Type")
|
||||
|| model
|
||||
.resolve_call_path(value)
|
||||
.as_ref()
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtins", "type"]))
|
||||
{
|
||||
is_base_exception(slice, semantic)
|
||||
is_base_exception(slice, model)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -49,14 +49,14 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct IterMethodReturnIterable {
|
||||
is_async: bool,
|
||||
async_: bool,
|
||||
}
|
||||
|
||||
impl Violation for IterMethodReturnIterable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let IterMethodReturnIterable { is_async } = self;
|
||||
if *is_async {
|
||||
let IterMethodReturnIterable { async_ } = self;
|
||||
if *async_ {
|
||||
format!("`__aiter__` methods should return an `AsyncIterator`, not an `AsyncIterable`")
|
||||
} else {
|
||||
format!("`__iter__` methods should return an `Iterator`, not an `Iterable`")
|
||||
@@ -67,31 +67,41 @@ impl Violation for IterMethodReturnIterable {
|
||||
/// PYI045
|
||||
pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &Definition) {
|
||||
let Definition::Member(Member {
|
||||
kind: MemberKind::Method(function),
|
||||
kind: MemberKind::Method,
|
||||
stmt,
|
||||
..
|
||||
}) = definition
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(returns) = function.returns.as_ref() else {
|
||||
let Stmt::FunctionDef(ast::StmtFunctionDef { name, returns, .. }) = stmt else {
|
||||
return;
|
||||
};
|
||||
|
||||
let is_async = match function.name.as_str() {
|
||||
let Some(returns) = returns else {
|
||||
return;
|
||||
};
|
||||
|
||||
let annotation = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = returns.as_ref() {
|
||||
// Ex) `Iterable[T]`
|
||||
value
|
||||
} else {
|
||||
// Ex) `Iterable`, `typing.Iterable`
|
||||
returns
|
||||
};
|
||||
|
||||
let async_ = match name.as_str() {
|
||||
"__iter__" => false,
|
||||
"__aiter__" => true,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Support both `Iterable` and `Iterable[T]`.
|
||||
let annotation = map_subscript(returns);
|
||||
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(map_subscript(annotation))
|
||||
.resolve_call_path(annotation)
|
||||
.is_some_and(|call_path| {
|
||||
if is_async {
|
||||
if async_ {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
["typing", "AsyncIterable"] | ["collections", "abc", "AsyncIterable"]
|
||||
@@ -105,7 +115,7 @@ pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &De
|
||||
})
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
IterMethodReturnIterable { is_async },
|
||||
IterMethodReturnIterable { async_ },
|
||||
returns.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ impl Violation for NonSelfReturnType {
|
||||
pub(crate) fn non_self_return_type(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
is_async: bool,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
returns: Option<&Expr>,
|
||||
parameters: &Parameters,
|
||||
async_: bool,
|
||||
) {
|
||||
let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
|
||||
let ScopeKind::Class(class_def) = checker.semantic().scope().kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -138,7 +138,7 @@ pub(crate) fn non_self_return_type(
|
||||
return;
|
||||
}
|
||||
|
||||
if is_async {
|
||||
if async_ {
|
||||
if name == "__aenter__"
|
||||
&& is_name(returns, &class_def.name)
|
||||
&& !is_final(&class_def.decorator_list, checker.semantic())
|
||||
|
||||
@@ -121,7 +121,7 @@ impl fmt::Display for ExprType {
|
||||
|
||||
/// Return the [`ExprType`] of an [`Expr]` if it is a builtin type (e.g. `int`, `bool`, `float`,
|
||||
/// `str`, `bytes`, or `complex`).
|
||||
fn match_builtin_type(expr: &Expr, semantic: &SemanticModel) -> Option<ExprType> {
|
||||
fn match_builtin_type(expr: &Expr, model: &SemanticModel) -> Option<ExprType> {
|
||||
let name = expr.as_name_expr()?;
|
||||
let result = match name.id.as_str() {
|
||||
"int" => ExprType::Int,
|
||||
@@ -132,7 +132,7 @@ fn match_builtin_type(expr: &Expr, semantic: &SemanticModel) -> Option<ExprType>
|
||||
"complex" => ExprType::Complex,
|
||||
_ => return None,
|
||||
};
|
||||
if !semantic.is_builtin(name.id.as_str()) {
|
||||
if !model.is_builtin(name.id.as_str()) {
|
||||
return None;
|
||||
}
|
||||
Some(result)
|
||||
|
||||
@@ -349,8 +349,8 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn is_special_assignment(target: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
match id.as_str() {
|
||||
"__all__" => semantic.current_scope().kind.is_module(),
|
||||
"__match_args__" | "__slots__" => semantic.current_scope().kind.is_class(),
|
||||
"__all__" => semantic.scope().kind.is_module(),
|
||||
"__match_args__" | "__slots__" => semantic.scope().kind.is_class(),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -569,9 +569,7 @@ pub(crate) fn unannotated_assignment_in_stub(
|
||||
return;
|
||||
}
|
||||
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
checker.semantic().current_scope().kind
|
||||
{
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = checker.semantic().scope().kind {
|
||||
if is_enum(arguments.as_deref(), checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,12 +96,11 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let stmt = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().current_statement_parent();
|
||||
let stmt = checker.semantic().stmt();
|
||||
let parent = checker.semantic().stmt_parent();
|
||||
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
|
||||
diagnostic.set_fix(
|
||||
Fix::automatic(edit)
|
||||
.isolate(checker.isolation(checker.semantic().current_statement_parent())),
|
||||
Fix::automatic(edit).isolate(checker.isolation(checker.semantic().stmt_parent())),
|
||||
);
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -43,7 +43,7 @@ impl AlwaysAutofixableViolation for StringOrBytesTooLong {
|
||||
/// PYI053
|
||||
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) {
|
||||
// Ignore docstrings.
|
||||
if is_docstring_stmt(checker.semantic().current_statement()) {
|
||||
if is_docstring_stmt(checker.semantic().stmt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::Imported;
|
||||
use ruff_python_semantic::{Binding, BindingKind};
|
||||
use ruff_python_semantic::{Binding, BindingKind, FromImport};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -51,10 +50,10 @@ pub(crate) fn unaliased_collections_abc_set_import(
|
||||
checker: &Checker,
|
||||
binding: &Binding,
|
||||
) -> Option<Diagnostic> {
|
||||
let BindingKind::FromImport(import) = &binding.kind else {
|
||||
let BindingKind::FromImport(FromImport { qualified_name }) = &binding.kind else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(import.call_path(), ["collections", "abc", "Set"]) {
|
||||
if qualified_name.as_str() != "collections.abc.Set" {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,7 @@ pub(crate) fn unused_private_type_var(
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) =
|
||||
checker.semantic().statement(source)
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -218,7 +217,7 @@ pub(crate) fn unused_private_protocol(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().statement(source) else {
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -261,7 +260,7 @@ pub(crate) fn unused_private_type_alias(
|
||||
};
|
||||
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
target, annotation, ..
|
||||
}) = checker.semantic().statement(source)
|
||||
}) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -305,7 +304,7 @@ pub(crate) fn unused_private_typed_dict(
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().statement(source) else {
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,44 +1,42 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI016.py:7:15: PYI016 [*] Duplicate union member `str`
|
||||
PYI016.py:5:15: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
6 | # Should emit for duplicate field types.
|
||||
7 | field2: str | str # PYI016: Duplicate union member `str`
|
||||
4 | # Should emit for duplicate field types.
|
||||
5 | field2: str | str # PYI016: Duplicate union member `str`
|
||||
| ^^^ PYI016
|
||||
8 |
|
||||
9 | # Should emit for union types in arguments.
|
||||
|
|
||||
= help: Remove duplicate union member `str`
|
||||
|
||||
ℹ Fix
|
||||
4 4 | field1: str
|
||||
5 5 |
|
||||
6 6 | # Should emit for duplicate field types.
|
||||
7 |-field2: str | str # PYI016: Duplicate union member `str`
|
||||
7 |+field2: str # PYI016: Duplicate union member `str`
|
||||
8 8 |
|
||||
9 9 | # Should emit for union types in arguments.
|
||||
10 10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
2 2 | field1: str
|
||||
3 3 |
|
||||
4 4 | # Should emit for duplicate field types.
|
||||
5 |-field2: str | str # PYI016: Duplicate union member `str`
|
||||
5 |+field2: str # PYI016: Duplicate union member `str`
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 8 | # Should emit for union types in arguments.
|
||||
|
||||
PYI016.py:10:23: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:9:23: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
9 | # Should emit for union types in arguments.
|
||||
10 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
8 | # Should emit for union types in arguments.
|
||||
9 | def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
11 | print(arg1)
|
||||
10 | print(arg1)
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
7 7 | field2: str | str # PYI016: Duplicate union member `str`
|
||||
8 8 |
|
||||
9 9 | # Should emit for union types in arguments.
|
||||
10 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
10 |+def func1(arg1: int): # PYI016: Duplicate union member `int`
|
||||
11 11 | print(arg1)
|
||||
6 6 |
|
||||
7 7 |
|
||||
8 8 | # Should emit for union types in arguments.
|
||||
9 |-def func1(arg1: int | int): # PYI016: Duplicate union member `int`
|
||||
9 |+def func1(arg1: int): # PYI016: Duplicate union member `int`
|
||||
10 10 | print(arg1)
|
||||
11 11 |
|
||||
12 12 |
|
||||
13 13 | # Should emit for unions in return types.
|
||||
|
||||
PYI016.py:14:22: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
@@ -50,494 +48,170 @@ PYI016.py:14:22: PYI016 [*] Duplicate union member `str`
|
||||
= help: Remove duplicate union member `str`
|
||||
|
||||
ℹ Fix
|
||||
11 11 | print(arg1)
|
||||
11 11 |
|
||||
12 12 |
|
||||
13 13 | # Should emit for unions in return types.
|
||||
14 |-def func2() -> str | str: # PYI016: Duplicate union member `str`
|
||||
14 |+def func2() -> str: # PYI016: Duplicate union member `str`
|
||||
15 15 | return "my string"
|
||||
16 16 |
|
||||
17 17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
17 17 |
|
||||
|
||||
PYI016.py:18:15: PYI016 [*] Duplicate union member `str`
|
||||
PYI016.py:19:15: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
18 | # Should emit in longer unions, even if not directly adjacent.
|
||||
19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
| ^^^ PYI016
|
||||
19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
|
|
||||
= help: Remove duplicate union member `str`
|
||||
|
||||
ℹ Fix
|
||||
15 15 | return "my string"
|
||||
16 16 |
|
||||
17 17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
18 |-field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
18 |+field3: str | int # PYI016: Duplicate union member `str`
|
||||
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
17 17 |
|
||||
18 18 | # Should emit in longer unions, even if not directly adjacent.
|
||||
19 |-field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 |+field3: str | int # PYI016: Duplicate union member `str`
|
||||
20 20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
22 22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
|
||||
PYI016.py:19:15: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:20:15: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
18 | # Should emit in longer unions, even if not directly adjacent.
|
||||
19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
16 16 |
|
||||
17 17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 |-field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
19 |+field4: int | str # PYI016: Duplicate union member `int`
|
||||
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
22 22 |
|
||||
17 17 |
|
||||
18 18 | # Should emit in longer unions, even if not directly adjacent.
|
||||
19 19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
20 |-field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 |+field4: int | str # PYI016: Duplicate union member `int`
|
||||
21 21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
22 22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
23 23 |
|
||||
|
||||
PYI016.py:20:21: PYI016 [*] Duplicate union member `str`
|
||||
PYI016.py:21:21: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
| ^^^ PYI016
|
||||
21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
|
|
||||
= help: Remove duplicate union member `str`
|
||||
|
||||
ℹ Fix
|
||||
17 17 | # Should emit in longer unions, even if not directly adjacent.
|
||||
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 |-field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
20 |+field5: str | int # PYI016: Duplicate union member `str`
|
||||
21 21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
22 22 |
|
||||
23 23 | # Shouldn't emit for non-type unions.
|
||||
18 18 | # Should emit in longer unions, even if not directly adjacent.
|
||||
19 19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
20 20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 |-field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 |+field5: str | int # PYI016: Duplicate union member `str`
|
||||
22 22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
23 23 |
|
||||
24 24 | # Shouldn't emit for non-type unions.
|
||||
|
||||
PYI016.py:21:28: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:22:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
22 | field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
22 |
|
||||
23 | # Shouldn't emit for non-type unions.
|
||||
23 |
|
||||
24 | # Shouldn't emit for non-type unions.
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
18 18 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
19 19 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
20 20 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
21 |-field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
21 |+field6: int | bool | str # PYI016: Duplicate union member `int`
|
||||
22 22 |
|
||||
23 23 | # Shouldn't emit for non-type unions.
|
||||
24 24 | field7 = str | str
|
||||
19 19 | field3: str | str | int # PYI016: Duplicate union member `str`
|
||||
20 20 | field4: int | int | str # PYI016: Duplicate union member `int`
|
||||
21 21 | field5: str | int | str # PYI016: Duplicate union member `str`
|
||||
22 |-field6: int | bool | str | int # PYI016: Duplicate union member `int`
|
||||
22 |+field6: int | bool | str # PYI016: Duplicate union member `int`
|
||||
23 23 |
|
||||
24 24 | # Shouldn't emit for non-type unions.
|
||||
25 25 | field7 = str | str
|
||||
|
||||
PYI016.py:27:22: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:28:22: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
26 | # Should emit for strangely-bracketed unions.
|
||||
27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
27 | # Should emit for strangely-bracketed unions.
|
||||
28 | field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
28 |
|
||||
29 | # Should handle user brackets when fixing.
|
||||
29 |
|
||||
30 | # Should handle user brackets when fixing.
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
24 24 | field7 = str | str
|
||||
25 25 |
|
||||
26 26 | # Should emit for strangely-bracketed unions.
|
||||
27 |-field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
27 |+field8: int | (str) # PYI016: Duplicate union member `int`
|
||||
28 28 |
|
||||
29 29 | # Should handle user brackets when fixing.
|
||||
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
25 25 | field7 = str | str
|
||||
26 26 |
|
||||
27 27 | # Should emit for strangely-bracketed unions.
|
||||
28 |-field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
28 |+field8: int | (str) # PYI016: Duplicate union member `int`
|
||||
29 29 |
|
||||
30 30 | # Should handle user brackets when fixing.
|
||||
31 31 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
|
||||
PYI016.py:30:16: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:31:16: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
29 | # Should handle user brackets when fixing.
|
||||
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
30 | # Should handle user brackets when fixing.
|
||||
31 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
32 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
27 27 | field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
28 28 |
|
||||
29 29 | # Should handle user brackets when fixing.
|
||||
30 |-field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
30 |+field9: int | (str) # PYI016: Duplicate union member `int`
|
||||
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
32 32 |
|
||||
33 33 | # Should emit for nested unions.
|
||||
28 28 | field8: int | (str | int) # PYI016: Duplicate union member `int`
|
||||
29 29 |
|
||||
30 30 | # Should handle user brackets when fixing.
|
||||
31 |-field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
31 |+field9: int | (str) # PYI016: Duplicate union member `int`
|
||||
32 32 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
33 33 |
|
||||
34 34 | # Should emit for nested unions.
|
||||
|
||||
PYI016.py:31:24: PYI016 [*] Duplicate union member `str`
|
||||
PYI016.py:32:24: PYI016 [*] Duplicate union member `str`
|
||||
|
|
||||
29 | # Should handle user brackets when fixing.
|
||||
30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
30 | # Should handle user brackets when fixing.
|
||||
31 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
32 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
| ^^^ PYI016
|
||||
32 |
|
||||
33 | # Should emit for nested unions.
|
||||
33 |
|
||||
34 | # Should emit for nested unions.
|
||||
|
|
||||
= help: Remove duplicate union member `str`
|
||||
|
||||
ℹ Fix
|
||||
28 28 |
|
||||
29 29 | # Should handle user brackets when fixing.
|
||||
30 30 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
31 |-field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
31 |+field10: str | int # PYI016: Duplicate union member `str`
|
||||
32 32 |
|
||||
33 33 | # Should emit for nested unions.
|
||||
34 34 | field11: dict[int | int, str]
|
||||
29 29 |
|
||||
30 30 | # Should handle user brackets when fixing.
|
||||
31 31 | field9: int | (int | str) # PYI016: Duplicate union member `int`
|
||||
32 |-field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
32 |+field10: str | int # PYI016: Duplicate union member `str`
|
||||
33 33 |
|
||||
34 34 | # Should emit for nested unions.
|
||||
35 35 | field11: dict[int | int, str]
|
||||
|
||||
PYI016.py:34:21: PYI016 [*] Duplicate union member `int`
|
||||
PYI016.py:35:21: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
33 | # Should emit for nested unions.
|
||||
34 | field11: dict[int | int, str]
|
||||
34 | # Should emit for nested unions.
|
||||
35 | field11: dict[int | int, str]
|
||||
| ^^^ PYI016
|
||||
35 |
|
||||
36 | # Should emit for unions with more than two cases
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
31 31 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
32 32 |
|
||||
33 33 | # Should emit for nested unions.
|
||||
34 |-field11: dict[int | int, str]
|
||||
34 |+field11: dict[int, str]
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 37 | field12: int | int | int # Error
|
||||
|
||||
PYI016.py:37:16: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
36 | # Should emit for unions with more than two cases
|
||||
37 | field12: int | int | int # Error
|
||||
| ^^^ PYI016
|
||||
38 | field13: int | int | int | int # Error
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
34 34 | field11: dict[int | int, str]
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 |-field12: int | int | int # Error
|
||||
37 |+field12: int | int # Error
|
||||
38 38 | field13: int | int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
||||
PYI016.py:37:22: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
36 | # Should emit for unions with more than two cases
|
||||
37 | field12: int | int | int # Error
|
||||
| ^^^ PYI016
|
||||
38 | field13: int | int | int | int # Error
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
34 34 | field11: dict[int | int, str]
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 |-field12: int | int | int # Error
|
||||
37 |+field12: int | int # Error
|
||||
38 38 | field13: int | int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
||||
PYI016.py:38:16: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
36 | # Should emit for unions with more than two cases
|
||||
37 | field12: int | int | int # Error
|
||||
38 | field13: int | int | int | int # Error
|
||||
| ^^^ PYI016
|
||||
39 |
|
||||
40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 37 | field12: int | int | int # Error
|
||||
38 |-field13: int | int | int | int # Error
|
||||
38 |+field13: int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 41 | field14: int | int | str | int # Error
|
||||
|
||||
PYI016.py:38:22: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
36 | # Should emit for unions with more than two cases
|
||||
37 | field12: int | int | int # Error
|
||||
38 | field13: int | int | int | int # Error
|
||||
| ^^^ PYI016
|
||||
39 |
|
||||
40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 37 | field12: int | int | int # Error
|
||||
38 |-field13: int | int | int | int # Error
|
||||
38 |+field13: int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 41 | field14: int | int | str | int # Error
|
||||
|
||||
PYI016.py:38:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
36 | # Should emit for unions with more than two cases
|
||||
37 | field12: int | int | int # Error
|
||||
38 | field13: int | int | int | int # Error
|
||||
| ^^^ PYI016
|
||||
39 |
|
||||
40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
35 35 |
|
||||
36 36 | # Should emit for unions with more than two cases
|
||||
37 37 | field12: int | int | int # Error
|
||||
38 |-field13: int | int | int | int # Error
|
||||
38 |+field13: int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 41 | field14: int | int | str | int # Error
|
||||
|
||||
PYI016.py:41:16: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 | field14: int | int | str | int # Error
|
||||
| ^^^ PYI016
|
||||
42 |
|
||||
43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
38 38 | field13: int | int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 |-field14: int | int | str | int # Error
|
||||
41 |+field14: int | str | int # Error
|
||||
42 42 |
|
||||
43 43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||
|
||||
PYI016.py:41:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 | field14: int | int | str | int # Error
|
||||
| ^^^ PYI016
|
||||
42 |
|
||||
43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
38 38 | field13: int | int | int | int # Error
|
||||
39 39 |
|
||||
40 40 | # Should emit for unions with more than two cases, even if not directly adjacent
|
||||
41 |-field14: int | int | str | int # Error
|
||||
41 |+field14: int | int | str # Error
|
||||
42 42 |
|
||||
43 43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
44 44 | field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||
|
||||
PYI016.py:44:30: PYI016 [*] Duplicate union member `typing.Literal[1]`
|
||||
|
|
||||
43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
44 | field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||
| ^^^^^^^^^^^^^^^^^ PYI016
|
||||
45 |
|
||||
46 | # Shouldn't emit if in new parent type
|
||||
|
|
||||
= help: Remove duplicate union member `typing.Literal[1]`
|
||||
|
||||
ℹ Fix
|
||||
41 41 | field14: int | int | str | int # Error
|
||||
42 42 |
|
||||
43 43 | # Should emit for duplicate literal types; also covered by PYI030
|
||||
44 |-field15: typing.Literal[1] | typing.Literal[1] # Error
|
||||
44 |+field15: typing.Literal[1] # Error
|
||||
45 45 |
|
||||
46 46 | # Shouldn't emit if in new parent type
|
||||
47 47 | field16: int | dict[int, str] # OK
|
||||
|
||||
PYI016.py:57:5: PYI016 Duplicate union member `set[int]`
|
||||
|
|
||||
55 | int # foo
|
||||
56 | ],
|
||||
57 | set[
|
||||
| _____^
|
||||
58 | | int # bar
|
||||
59 | | ],
|
||||
| |_____^ PYI016
|
||||
60 | ] # Error, newline and comment will not be emitted in message
|
||||
|
|
||||
= help: Remove duplicate union member `set[int]`
|
||||
|
||||
PYI016.py:63:28: PYI016 Duplicate union member `int`
|
||||
|
|
||||
62 | # Should emit in cases with `typing.Union` instead of `|`
|
||||
63 | field19: typing.Union[int, int] # Error
|
||||
| ^^^ PYI016
|
||||
64 |
|
||||
65 | # Should emit in cases with nested `typing.Union`
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:66:41: PYI016 Duplicate union member `int`
|
||||
|
|
||||
65 | # Should emit in cases with nested `typing.Union`
|
||||
66 | field20: typing.Union[int, typing.Union[int, str]] # Error
|
||||
| ^^^ PYI016
|
||||
67 |
|
||||
68 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:69:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
68 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||
69 | field21: typing.Union[int, int | str] # Error
|
||||
| ^^^ PYI016
|
||||
70 |
|
||||
71 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
66 66 | field20: typing.Union[int, typing.Union[int, str]] # Error
|
||||
67 67 |
|
||||
68 68 | # Should emit in cases with mixed `typing.Union` and `|`
|
||||
69 |-field21: typing.Union[int, int | str] # Error
|
||||
69 |+field21: typing.Union[int, str] # Error
|
||||
70 70 |
|
||||
71 71 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
72 72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
|
||||
PYI016.py:72:41: PYI016 Duplicate union member `int`
|
||||
|
|
||||
71 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
| ^^^ PYI016
|
||||
73 |
|
||||
74 | # Should emit in cases with newlines
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:72:59: PYI016 Duplicate union member `int`
|
||||
|
|
||||
71 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
| ^^^ PYI016
|
||||
73 |
|
||||
74 | # Should emit in cases with newlines
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:72:64: PYI016 Duplicate union member `int`
|
||||
|
|
||||
71 | # Should emit only once in cases with multiple nested `typing.Union`
|
||||
72 | field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
|
||||
| ^^^ PYI016
|
||||
73 |
|
||||
74 | # Should emit in cases with newlines
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:76:12: PYI016 [*] Duplicate union member `set[int]`
|
||||
|
|
||||
74 | # Should emit in cases with newlines
|
||||
75 | field23: set[ # foo
|
||||
76 | int] | set[int]
|
||||
| ^^^^^^^^ PYI016
|
||||
77 |
|
||||
78 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `set[int]`
|
||||
|
||||
ℹ Fix
|
||||
73 73 |
|
||||
74 74 | # Should emit in cases with newlines
|
||||
75 75 | field23: set[ # foo
|
||||
76 |- int] | set[int]
|
||||
76 |+ int]
|
||||
77 77 |
|
||||
78 78 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
79 79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
|
||||
PYI016.py:81:41: PYI016 Duplicate union member `int`
|
||||
|
|
||||
79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
80 | # we incorrectly re-checked the nested union).
|
||||
81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
82 |
|
||||
83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:81:46: PYI016 Duplicate union member `int`
|
||||
|
|
||||
79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
80 | # we incorrectly re-checked the nested union).
|
||||
81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
82 |
|
||||
83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.py:86:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 | # we incorrectly re-checked the nested union).
|
||||
86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 85 | # we incorrectly re-checked the nested union).
|
||||
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
|
||||
|
||||
PYI016.py:86:34: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 | # we incorrectly re-checked the nested union).
|
||||
86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 85 | # we incorrectly re-checked the nested union).
|
||||
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
|
||||
32 32 | field10: (str | int) | str # PYI016: Duplicate union member `str`
|
||||
33 33 |
|
||||
34 34 | # Should emit for nested unions.
|
||||
35 |-field11: dict[int | int, str]
|
||||
35 |+field11: dict[int, str]
|
||||
|
||||
|
||||
|
||||
@@ -471,8 +471,6 @@ PYI016.pyi:76:12: PYI016 [*] Duplicate union member `set[int]`
|
||||
75 | field23: set[ # foo
|
||||
76 | int] | set[int]
|
||||
| ^^^^^^^^ PYI016
|
||||
77 |
|
||||
78 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `set[int]`
|
||||
|
||||
@@ -482,62 +480,5 @@ PYI016.pyi:76:12: PYI016 [*] Duplicate union member `set[int]`
|
||||
75 75 | field23: set[ # foo
|
||||
76 |- int] | set[int]
|
||||
76 |+ int]
|
||||
77 77 |
|
||||
78 78 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
79 79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
|
||||
PYI016.pyi:81:41: PYI016 Duplicate union member `int`
|
||||
|
|
||||
79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
80 | # we incorrectly re-checked the nested union).
|
||||
81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
82 |
|
||||
83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.pyi:81:46: PYI016 Duplicate union member `int`
|
||||
|
|
||||
79 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
80 | # we incorrectly re-checked the nested union).
|
||||
81 | field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
82 |
|
||||
83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
PYI016.pyi:86:28: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 | # we incorrectly re-checked the nested union).
|
||||
86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 85 | # we incorrectly re-checked the nested union).
|
||||
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
|
||||
|
||||
PYI016.pyi:86:34: PYI016 [*] Duplicate union member `int`
|
||||
|
|
||||
84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 | # we incorrectly re-checked the nested union).
|
||||
86 | field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
| ^^^ PYI016
|
||||
|
|
||||
= help: Remove duplicate union member `int`
|
||||
|
||||
ℹ Fix
|
||||
83 83 | # Should emit twice (once for each `int` in the nested union, both of which are
|
||||
84 84 | # duplicates of the outer `int`), but not three times (which would indicate that
|
||||
85 85 | # we incorrectly re-checked the nested union).
|
||||
86 |-field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
86 |+field25: typing.Union[int, int] # PYI016: Duplicate union member `int`
|
||||
|
||||
|
||||
|
||||
@@ -21,24 +21,17 @@ PYI019.py:14:54: PYI019 Methods like `bad_class_method` should return `typing.Se
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.py:35:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
17 | @classmethod
|
||||
18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
37 | # Python > 3.12
|
||||
38 | class PEP695BadDunderNew[T]:
|
||||
39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
33 | # Python > 3.12
|
||||
34 | class PEP695BadDunderNew[T]:
|
||||
35 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.py:38:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
42 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
38 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
|
||||
@@ -21,24 +21,17 @@ PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `typing.S
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.pyi:35:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
17 | @classmethod
|
||||
18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
37 | # Python > 3.12
|
||||
38 | class PEP695BadDunderNew[T]:
|
||||
39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
33 | # Python > 3.12
|
||||
34 | class PEP695BadDunderNew[T]:
|
||||
35 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.pyi:38:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
42 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
38 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
|
||||
@@ -246,8 +246,9 @@ pub(crate) fn unittest_assertion(
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// We're converting an expression to a statement, so avoid applying the fix if
|
||||
// the assertion is part of a larger expression.
|
||||
if checker.semantic().current_statement().is_expr_stmt()
|
||||
&& checker.semantic().current_expression_parent().is_none()
|
||||
if checker.semantic().stmt().is_expr_stmt()
|
||||
&& checker.semantic().expr_parent().is_none()
|
||||
&& !checker.semantic().scope().kind.is_lambda()
|
||||
&& !checker.indexer().comment_ranges().intersects(expr.range())
|
||||
{
|
||||
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user