Skip to content

Commit 261d69d

Browse files
committed
Merge branch 'main' into issue-848
* main: [`ruff`] Preserve relative whitespace in multi-line expressions (`RUF033`) (astral-sh#19647) [ty] Optimize TDD atom ordering (astral-sh#20098) [`airflow`] Extend `AIR311` and `AIR312` rules (astral-sh#20082) [ty] Preserve qualifiers when accessing attributes on unions/intersections (astral-sh#20114) [ty] Fix the inferred interface of specialized generic protocols (astral-sh#19866) [ty] Infer slightly more precise types for comprehensions (astral-sh#20111) [ty] Add more tests for protocols (astral-sh#20095) [ty] don't eagerly unpack aliases in user-authored unions (astral-sh#20055) [`flake8-use-pathlib`] Update links to the table showing the correspondence between `os` and `pathlib` (astral-sh#20103) [`flake8-use-pathlib`] Make `PTH100` fix unsafe because it can change behavior (astral-sh#20100) [`flake8-use-pathlib`] Delete unused `Rule::OsSymlink` enabled check (astral-sh#20099) [ty] Add search paths info to unresolved import diagnostics (astral-sh#20040) [`flake8-logging-format`] Add auto-fix for f-string logging calls (`G004`) (astral-sh#19303) Add a `ScopeKind` for the `__class__` cell (astral-sh#20048) Fix incorrect D413 links in docstrings convention FAQ (astral-sh#20089) [ty] Refactor inlay hints structure to use separate parts (astral-sh#20052)
2 parents d065cc6 + 89ca493 commit 261d69d

File tree

107 files changed

+3030
-849
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+3030
-849
lines changed

crates/ruff_linter/resources/test/fixtures/airflow/AIR311_names.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,36 @@
7474

7575
# airflow.utils.dag_parsing_context
7676
get_parsing_context()
77+
78+
from airflow.decorators.base import (
79+
DecoratedMappedOperator,
80+
DecoratedOperator,
81+
TaskDecorator,
82+
get_unique_task_id,
83+
task_decorator_factory,
84+
)
85+
86+
# airflow.decorators.base
87+
DecoratedMappedOperator()
88+
DecoratedOperator()
89+
TaskDecorator()
90+
get_unique_task_id()
91+
task_decorator_factory()
92+
93+
94+
from airflow.models import Param
95+
96+
# airflow.models
97+
Param()
98+
99+
100+
from airflow.sensors.base import (
101+
BaseSensorOperator,
102+
PokeReturnValue,
103+
poke_mode_only,
104+
)
105+
106+
# airflow.sensors.base
107+
BaseSensorOperator()
108+
PokeReturnValue()
109+
poke_mode_only()

crates/ruff_linter/resources/test/fixtures/airflow/AIR312.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from airflow.operators.latest_only import LatestOnlyOperator
1010
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
1111
from airflow.operators.weekday import BranchDayOfWeekOperator
12-
from airflow.sensors.date_time import DateTimeSensor
1312

1413
FSHook()
1514
PackageIndexHook()
@@ -22,24 +21,30 @@
2221

2322
LatestOnlyOperator()
2423
BranchDayOfWeekOperator()
25-
DateTimeSensor()
2624

2725
from airflow.operators.python import (
2826
BranchPythonOperator,
2927
PythonOperator,
3028
PythonVirtualenvOperator,
3129
ShortCircuitOperator,
3230
)
31+
from airflow.sensors.bash import BashSensor
32+
from airflow.sensors.date_time import DateTimeSensor
33+
34+
BranchPythonOperator()
35+
PythonOperator()
36+
PythonVirtualenvOperator()
37+
ShortCircuitOperator()
38+
39+
BashSensor()
40+
DateTimeSensor()
3341
from airflow.sensors.date_time import DateTimeSensorAsync
3442
from airflow.sensors.external_task import (
3543
ExternalTaskMarker,
3644
ExternalTaskSensor,
3745
)
38-
from airflow.sensors.time_sensor import (
39-
TimeSensor,
40-
TimeSensorAsync,
41-
)
4246
from airflow.sensors.filesystem import FileSensor
47+
from airflow.sensors.python import PythonSensor
4348

4449
BranchPythonOperator()
4550
PythonOperator()
@@ -49,6 +54,13 @@
4954
ExternalTaskMarker()
5055
ExternalTaskSensor()
5156
FileSensor()
57+
PythonSensor()
58+
59+
from airflow.sensors.time_sensor import (
60+
TimeSensor,
61+
TimeSensorAsync,
62+
)
63+
5264
TimeSensor()
5365
TimeSensorAsync()
5466

crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,50 @@
1717
# Don't trigger for t-strings
1818
info(t"{name}")
1919
info(t"{__name__}")
20+
21+
count = 5
22+
total = 9
23+
directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24+
logging.info(f"{count} out of {total} files in {directory_path} checked")
25+
26+
27+
28+
x = 99
29+
fmt = "08d"
30+
logger.info(f"{x:{'08d'}}")
31+
logger.info(f"{x:>10} {x:{fmt}}")
32+
33+
logging.info(f"")
34+
logging.info(f"This message doesn't have any variables.")
35+
36+
obj = {"key": "value"}
37+
logging.info(f"Object: {obj!r}")
38+
39+
items_count = 3
40+
logging.warning(f"Items: {items_count:d}")
41+
42+
data = {"status": "active"}
43+
logging.info(f"Processing {len(data)} items")
44+
logging.info(f"Status: {data.get('status', 'unknown').upper()}")
45+
46+
47+
result = 123
48+
logging.info(f"Calculated result: {result + 100}")
49+
50+
temperature = 123
51+
logging.info(f"Temperature: {temperature:.1f}°C")
52+
53+
class FilePath:
54+
def __init__(self, name: str):
55+
self.name = name
56+
57+
logging.info(f"No changes made to {file_path.name}.")
58+
59+
user = "tron"
60+
balance = 123.45
61+
logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
62+
63+
import logging
64+
65+
x = 1
66+
logging.error(f"{x} -> %s", x)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Test f-string argument order."""
2+
3+
import logging
4+
5+
logger = logging.getLogger(__name__)
6+
7+
X = 1
8+
Y = 2
9+
logger.error(f"{X} -> %s", Y)
10+
logger.error(f"{Y} -> %s", X)

crates/ruff_linter/resources/test/fixtures/pyflakes/F841_0.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,39 @@ def f():
151151
pass
152152
except Exception as _:
153153
pass
154+
155+
156+
# OK, `__class__` in this case is not the special `__class__` cell, so we don't
157+
# emit a diagnostic. (It has its own special semantics -- see
158+
# https://github.com/astral-sh/ruff/pull/20048#discussion_r2298338048 -- but
159+
# those aren't relevant here.)
160+
class A:
161+
__class__ = 1
162+
163+
164+
# The following three cases are flagged because they declare local `__class__`
165+
# variables that don't refer to the special `__class__` cell.
166+
class A:
167+
def set_class(self, cls):
168+
__class__ = cls # F841
169+
170+
171+
class A:
172+
class B:
173+
def set_class(self, cls):
174+
__class__ = cls # F841
175+
176+
177+
class A:
178+
def foo():
179+
class B:
180+
print(__class__)
181+
def set_class(self, cls):
182+
__class__ = cls # F841
183+
184+
185+
# OK, the `__class__` cell is nonlocal and declared as such.
186+
class NonlocalDunderClass:
187+
def foo():
188+
nonlocal __class__
189+
__class__ = 1

crates/ruff_linter/resources/test/fixtures/pylint/nonlocal_without_binding.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ def f():
4444
def g():
4545
nonlocal x
4646
x = 2
47+
48+
# OK
49+
class A:
50+
def method(self):
51+
nonlocal __class__

crates/ruff_linter/resources/test/fixtures/ruff/RUF033.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,19 @@ def __post_init__(
124124
...
125125

126126
return Foo
127+
128+
129+
@dataclass
130+
class C:
131+
def __post_init__(self, x: tuple[int, ...] = (
132+
1,
133+
2,
134+
)) -> None:
135+
self.x = x
136+
137+
138+
@dataclass
139+
class D:
140+
def __post_init__(self, x: int = """
141+
""") -> None:
142+
self.x = x

crates/ruff_linter/src/checkers/ast/analyze/expression.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
10461046
Rule::PyPath,
10471047
Rule::Glob,
10481048
Rule::OsListdir,
1049-
Rule::OsSymlink,
10501049
]) {
10511050
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
10521051
}

crates/ruff_linter/src/checkers/ast/mod.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,10 @@ impl SemanticSyntaxContext for Checker<'_> {
703703
match scope.kind {
704704
ScopeKind::Class(_) | ScopeKind::Lambda(_) => return false,
705705
ScopeKind::Function(ast::StmtFunctionDef { is_async, .. }) => return *is_async,
706-
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
706+
ScopeKind::Generator { .. }
707+
| ScopeKind::Module
708+
| ScopeKind::Type
709+
| ScopeKind::DunderClassCell => {}
707710
}
708711
}
709712
false
@@ -714,7 +717,10 @@ impl SemanticSyntaxContext for Checker<'_> {
714717
match scope.kind {
715718
ScopeKind::Class(_) => return false,
716719
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
717-
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
720+
ScopeKind::Generator { .. }
721+
| ScopeKind::Module
722+
| ScopeKind::Type
723+
| ScopeKind::DunderClassCell => {}
718724
}
719725
}
720726
false
@@ -725,7 +731,7 @@ impl SemanticSyntaxContext for Checker<'_> {
725731
match scope.kind {
726732
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
727733
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
728-
ScopeKind::Module | ScopeKind::Type => {}
734+
ScopeKind::Module | ScopeKind::Type | ScopeKind::DunderClassCell => {}
729735
}
730736
}
731737
false
@@ -1092,6 +1098,24 @@ impl<'a> Visitor<'a> for Checker<'a> {
10921098
}
10931099
}
10941100

1101+
// Here we add the implicit scope surrounding a method which allows code in the
1102+
// method to access `__class__` at runtime. See the `ScopeKind::DunderClassCell`
1103+
// docs for more information.
1104+
let added_dunder_class_scope = if self.semantic.current_scope().kind.is_class() {
1105+
self.semantic.push_scope(ScopeKind::DunderClassCell);
1106+
let binding_id = self.semantic.push_binding(
1107+
TextRange::default(),
1108+
BindingKind::DunderClassCell,
1109+
BindingFlags::empty(),
1110+
);
1111+
self.semantic
1112+
.current_scope_mut()
1113+
.add("__class__", binding_id);
1114+
true
1115+
} else {
1116+
false
1117+
};
1118+
10951119
self.semantic.push_scope(ScopeKind::Type);
10961120

10971121
if let Some(type_params) = type_params {
@@ -1155,6 +1179,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
11551179
self.semantic.pop_scope(); // Function scope
11561180
self.semantic.pop_definition();
11571181
self.semantic.pop_scope(); // Type parameter scope
1182+
if added_dunder_class_scope {
1183+
self.semantic.pop_scope(); // `__class__` cell closure scope
1184+
}
11581185
self.add_binding(
11591186
name,
11601187
stmt.identifier(),

crates/ruff_linter/src/preview.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
4040
settings.preview.is_enabled()
4141
}
4242

43+
/// <https://github.com/astral-sh/ruff/pull/19303>
44+
pub(crate) const fn is_fix_f_string_logging_enabled(settings: &LinterSettings) -> bool {
45+
settings.preview.is_enabled()
46+
}
47+
4348
// https://github.com/astral-sh/ruff/pull/16719
4449
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
4550
settings.preview.is_enabled()

0 commit comments

Comments
 (0)