Compare commits

...

20 Commits

Author SHA1 Message Date
David Peter
7e5358de2b [red-knot] T is disjoint from ~T 2025-02-10 22:15:47 +01:00
Dhruv Manilawala
1f3ff48b4f Undeprecate ruff.nativeServer (#16039)
Related to https://github.com/astral-sh/ruff-vscode/pull/684.
2025-02-08 22:29:07 +05:30
Carlos Martin
5e027a43ff Add JAX to users list. (#16031)
This PR adds [JAX](https://github.com/jax-ml/jax) to the
[list](https://github.com/astral-sh/ruff?tab=readme-ov-file#whos-using-ruff)
of open-source projects using Ruff.
2025-02-08 16:45:30 +00:00
Alex Waygood
22728808aa [pyupgrade] Ensure we do not rename two type parameters to the same name (UP049) (#16038)
Fixes #16024

## Summary

This PR adds proper isolation for `UP049` fixes so that two type
parameters are not renamed to the same name, which would introduce
invalid syntax. E.g. for this:

```py
class Foo[_T, __T]: ...
```

we cannot apply two autofixes to the class, as that would produce
invalid syntax -- this:

```py
class Foo[T, T]: ...
```

The "isolation" here means that Ruff won't apply more than one fix to
the same type-parameter list in a single iteration of the loop it does
to apply all autofixes. This means that after the first autofix has been
done, the semantic model will have recalculated which variables are
available in the scope, meaning that the diagnostic for the second
parameter will be deemed unfixable since it collides with an existing
name in the same scope (the name we autofixed the first parameter to in
an earlier iteration of the autofix loop).

Cc. @ntBre, for interest!

## Test Plan

I added an integration test that reproduces the bug on `main`.
2025-02-08 15:44:04 +00:00
InSync
a04ddf2a55 [pyupgrade] [ruff] Don't apply renamings if the new name is shadowed in a scope of one of the references to the binding (UP049, RUF052) (#16032)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-02-08 11:25:23 +00:00
Dylan
3a806ecaa1 [flake8-annotations] Correct syntax for typing.Union in suggested return type fixes for ANN20x rules (#16025)
When suggesting a return type as a union in Python <=3.9, we now avoid a
`TypeError` by correctly suggesting syntax like `Union[int,str,None]`
instead of `Union[int | str | None]`.
2025-02-07 17:17:20 -06:00
InSync
a29009e4ed [pyupgrade] Comments within parenthesized value ranges should not affect applicability (UP040) (#16027)
## Summary

Follow-up to #16026.

Previously, the fix for this would be marked as unsafe, even though all
comments are preserved:

```python
# .pyi
T: TypeAlias = (  # Comment
	int | str
)
```

Now it is safe: comments within the parenthesized range no longer affect
applicability.

## Test Plan

`cargo nextest run` and `cargo insta test`.

---------

Co-authored-by: Dylan <53534755+dylwil3@users.noreply.github.com>
2025-02-07 14:44:33 -06:00
InSync
19f3424a1a [pylint] Do not report calls when object type and argument type mismatch, remove custom escape handling logic (PLE1310) (#15984)
## Summary

Resolves #15968.

Previously, these would be considered violations:

```python
b''.strip('//')
''.lstrip('//', foo = "bar")
```

...while these are not:

```python
b''.strip(b'//')
''.strip('\\b\\x08')
```

Ruff will now not report when the types of the object and that of the
argument mismatch, or when there are extra arguments.

## Test Plan

`cargo nextest run` and `cargo insta test`.
2025-02-07 14:31:07 -06:00
Brent Westbrook
d4a5772d96 [flake8-builtins] Match upstream module name comparison (A005) (#16006)
See #15951 for the original discussion and reviews. This is just the
first half of that PR (reaching parity with `flake8-builtins` without
adding any new configuration options) split out for nicer changelog
entries.

For posterity, here's a script for generating the module structure that
was useful for interactive testing and creating the table
[here](https://github.com/astral-sh/ruff/pull/15951#issuecomment-2640662041).
The results for this branch are the same as the `Strict` column there,
as expected.

```shell
mkdir abc collections foobar urlparse

for i in */
do
	touch $i/__init__.py
done	

cp -r abc foobar collections/.
cp -r abc collections foobar/.

touch ruff.toml

touch foobar/logging.py
```

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-02-07 13:55:56 -05:00
Alex Waygood
efa8a3ddcc [pyupgrade] Don't introduce invalid syntax when upgrading old-style type aliases with parenthesized multiline values (UP040) (#16026) 2025-02-07 17:05:17 +00:00
Dylan
46fe17767d Pass Checker by immutable reference to lint rules (#16012)
This very large PR changes the field `.diagnostics` in the `Checker`
from a `Vec<Diagnostic>` to a `RefCell<Vec<Diagnostic>>`, adds methods
to push new diagnostics to this cell, and then removes unnecessary
mutability throughout all of our lint rule implementations.

Consequently, the compiler may now enforce what was, till now, the
_convention_ that the only changes to the `Checker` that can happen
during a lint are the addition of diagnostics[^1].

The PR is best reviewed commit-by-commit. I have tried to keep the large
commits limited to "bulk actions that you can easily see are performing
the same find/replace on a large number of files", and separate anything
ad-hoc or with larger diffs. Please let me know if there's anything else
I can do to make this easier to review!

Many thanks to [`ast-grep`](https://github.com/ast-grep/ast-grep),
[`helix`](https://github.com/helix-editor/helix), and good ol'
fashioned`git` magic, without which this PR would have taken the rest of
my natural life.

[^1]: And randomly also the seen variables violating `flake8-bugbear`?
2025-02-07 09:05:50 -06:00
David Peter
1f7a29d347 [red-knot] Unpacker: Make invariant explicit and directly return a Type (#16018)
## Summary

- Do not return `Option<Type<…>>` from `Unpacker::get`, but just `Type`.
Panic otherwise.
- Rename `Unpacker::get` to `Unpacker::expression_type`
2025-02-07 12:00:04 +00:00
Wei Lee
618bfaf884 [airflow] Add external_task.{ExternalTaskMarker, ExternalTaskSensor} for AIR302 (#16014)
## Summary

Apply suggestions similar to
https://github.com/astral-sh/ruff/pull/15922#discussion_r1940697704


## Test Plan

a test fixture has been updated
2025-02-07 16:38:34 +05:30
Alex Waygood
b1c61cb2ee [ruff] Fix invalid annotation in docs example (#16016) 2025-02-07 10:45:51 +00:00
David Peter
97e6fc3793 [red-knot] Unpacking and for loop assignments to attributes (#16004)
## Summary

* Support assignments to attributes in more cases:
    - assignments in `for` loops
    - in unpacking assignments
* Add test for multi-target assignments
* Add tests for all other possible assignments to attributes that could
   possibly occur (in decreasing order of likeliness):
    - augmented attribute assignments
    - attribute assignments in `with` statements
    - attribute assignments in comprehensions
- Note: assignments to attributes in named expressions are not
   syntactically allowed

closes #15962

## Test Plan

New Markdown tests
2025-02-07 11:30:51 +01:00
Micha Reiser
38351e00ee [red-knot] Partial revert of relative import handling for files in the root of a search path (#16001)
## Summary

This PR reverts the behavior changes from
https://github.com/astral-sh/ruff/pull/15990

But it isn't just a revert, it also:

* Adds a test covering this specific behavior
* Preserves the improvement to use `saturating_sub` in the package case
to avoid overflows in the case of invalid syntax
* Use `ancestors` instead of a `for` loop

## Test Plan

Added test
2025-02-07 11:04:09 +01:00
Micha Reiser
26c37b1e0e Add knot.toml schema (#15735)
## Summary

Adds a JSON schema generation step for Red Knot. This PR doesn't yet add
a publishing step because it's still a bit early for that


## Test plan

I tested the schema in Zed, VS Code and PyCharm:

* PyCharm: You have to manually add a schema mapping (settings JSON
Schema Mappings)
* Zed and VS code support the inline schema specification

```toml
#:schema /Users/micha/astral/ruff/knot.schema.json


[environment]
extra-paths = []


[rules]
call-possibly-unbound-method = "error"
unknown-rule = "error"

# duplicate-base = "error"
```

```json
{
    "$schema": "file:///Users/micha/astral/ruff/knot.schema.json",

    "environment": {
        "python-version": "3.13",
        "python-platform": "linux2"
    },

    "rules": {
        "unknown-rule": "error"
    }
}
```


https://github.com/user-attachments/assets/a18fcd96-7cbe-4110-985b-9f1935584411


The Schema overall works but all editors have their own quirks:

* PyCharm: Hovering a name always shows the section description instead
of the description of the specific setting. But it's the same for other
settings in `pyproject.toml` files 🤷
* VS Code (JSON): Using the generated schema in a JSON file gives
exactly the experience I want
* VS Code (TOML): 
* Properties with multiple possible values are repeated during
auto-completion without giving any hint how they're different. ![Screen
Shot 2025-02-06 at 14 05 35
PM](https://github.com/user-attachments/assets/d7f3c2a9-2351-4226-9fc1-b91aa192a237)
* The property description mushes together the description of the
property and the value, which looks sort of ridiculous. ![Screen Shot
2025-02-06 at 14 04 40
PM](https://github.com/user-attachments/assets/8b72f04a-c62a-49b5-810f-7ddd472884d0)
* Autocompletion and documentation hovering works (except the
limitations mentioned above)
* Zed:
* Very similar to VS Code with the exception that it uses the
description attribute to distinguish settings with multiple possible
values ![Screen Shot 2025-02-06 at 14 08 19
PM](https://github.com/user-attachments/assets/78a7f849-ff4e-44ff-8317-708eaf02dc1f)


I don't think there's much we can do here other than hope (or help)
editors improve their auto completion. The same short comings also apply
to ruff, so this isn't something new. For now, I think this is good
enough
2025-02-07 10:59:40 +01:00
InSync
7db5a924af [flake8-comprehensions] Detect overshadowed list/set/dict, ignore variadics and named expressions (C417) (#15955)
## Summary

Part of #15809 and #15876.

This change brings several bugfixes:

* The nested `map()` call in `list(map(lambda x: x, []))` where `list`
is overshadowed is now correctly reported.
* The call will no longer reported if:
	* Any arguments given to `map()` are variadic.
	* Any of the iterables contain a named expression.

## Test Plan

`cargo nextest run` and `cargo insta test`.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-02-07 08:58:05 +00:00
Junhson Jean-Baptiste
349f93389e [flake8-simplify] Only trigger SIM401 on known dictionaries (SIM401) (#15995)
## Summary

This change resolves #15814 to ensure that `SIM401` is only triggered on
known dictionary types. Before, the rule was getting triggered even on
types that _resemble_ a dictionary but are not actually a dictionary.

I did this using the `is_known_to_be_of_type_dict(...)` functionality.
The logic for this function was duplicated in a few spots, so I moved
the code to a central location, removed redundant definitions, and
updated existing calls to use the single definition of the function!

## Test Plan

Since this PR only modifies an existing rule, I made changes to the
existing test instead of adding new ones. I made sure that `SIM401` is
triggered on types that are clearly dictionaries and that it's not
triggered on a simple custom dictionary-like type (using a modified
version of [the code in the issue](#15814))

The additional changes to de-duplicate `is_known_to_be_of_type_dict`
don't break any existing tests -- I think this should be fine since the
logic remains the same (please let me know if you think otherwise, I'm
excited to get feedback and work towards a good fix 🙂).

---------

Co-authored-by: Junhson Jean-Baptiste <junhsonjb@naan.mynetworksettings.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-02-07 08:25:20 +00:00
InSync
bb979e05ac [flake8-pie] Remove following comma correctly when the unpacked dictionary is empty (PIE800) (#16008)
## Summary

Resolves #15997.

Ruff used to introduce syntax errors while fixing these cases, but no
longer will:

```python
{"a": [], **{},}
#         ^^^^ Removed, leaving two contiguous commas

{"a": [], **({})}
#         ^^^^^ Removed, leaving a stray closing parentheses
```

Previously, the function would take a shortcut if the unpacked
dictionary is empty; now, both cases are handled using the same logic
introduced in #15394. This change slightly modifies that logic to also
remove the first comma following the dictionary, if and only if it is
empty.

## Test Plan

`cargo nextest run` and `cargo insta test`.
2025-02-07 08:52:10 +01:00
619 changed files with 5252 additions and 3634 deletions

4
Cargo.lock generated
View File

@@ -2439,6 +2439,7 @@ dependencies = [
"ruff_text_size",
"rustc-hash 2.1.0",
"salsa",
"schemars",
"serde",
"thiserror 2.0.11",
"toml",
@@ -2478,6 +2479,7 @@ dependencies = [
"ruff_text_size",
"rustc-hash 2.1.0",
"salsa",
"schemars",
"serde",
"smallvec",
"static_assertions",
@@ -2768,6 +2770,7 @@ dependencies = [
"ruff_text_size",
"rustc-hash 2.1.0",
"salsa",
"schemars",
"serde",
"tempfile",
"thiserror 2.0.11",
@@ -2792,6 +2795,7 @@ dependencies = [
"libcst",
"pretty_assertions",
"rayon",
"red_knot_project",
"regex",
"ruff",
"ruff_diagnostics",

View File

@@ -452,6 +452,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
- [Ibis](https://github.com/ibis-project/ibis)
- [ivy](https://github.com/unifyai/ivy)
- [JAX](https://github.com/jax-ml/jax)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Kraken Tech](https://kraken.tech/)
- [LangChain](https://github.com/hwchase17/langchain)

View File

@@ -28,6 +28,7 @@ pep440_rs = { workspace = true }
rayon = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
@@ -40,8 +41,9 @@ insta = { workspace = true, features = ["redactions", "ron"] }
[features]
default = ["zstd"]
zstd = ["red_knot_vendored/zstd"]
deflate = ["red_knot_vendored/deflate"]
schemars = ["dep:schemars", "ruff_db/schemars", "red_knot_python_semantic/schemars"]
zstd = ["red_knot_vendored/zstd"]
[lints]
workspace = true

View File

@@ -18,13 +18,16 @@ use thiserror::Error;
/// The options for the project.
#[derive(Debug, Default, Clone, PartialEq, Eq, Combine, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {
/// Configures the type checking environment.
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<EnvironmentOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub src: Option<SrcOptions>,
/// Configures the enabled lints and their severity.
#[serde(skip_serializing_if = "Option::is_none")]
pub rules: Option<Rules>,
}
@@ -177,10 +180,22 @@ impl Options {
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct EnvironmentOptions {
/// Specifies the version of Python that will be used to execute the source code.
/// The version should be specified as a string in the format `M.m` where `M` is the major version
/// and `m` is the minor (e.g. "3.0" or "3.6").
/// If a version is provided, knot will generate errors if the source code makes use of language features
/// that are not supported in that version.
/// It will also tailor its use of type stub files, which conditionalizes type definitions based on the version.
#[serde(skip_serializing_if = "Option::is_none")]
pub python_version: Option<RangedValue<PythonVersion>>,
/// Specifies the target platform that will be used to execute the source code.
/// If specified, Red Knot will tailor its use of type stub files,
/// which conditionalize type definitions based on the platform.
///
/// If no platform is specified, knot will use `all` or the current platform in the LSP use case.
#[serde(skip_serializing_if = "Option::is_none")]
pub python_platform: Option<RangedValue<PythonPlatform>>,
@@ -204,6 +219,7 @@ pub struct EnvironmentOptions {
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SrcOptions {
/// The root of the project, used for finding first-party modules.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -212,7 +228,9 @@ pub struct SrcOptions {
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", transparent)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Rules {
#[cfg_attr(feature = "schemars", schemars(with = "schema::Rules"))]
inner: FxHashMap<RangedValue<String>, RangedValue<Level>>,
}
@@ -226,6 +244,69 @@ impl FromIterator<(RangedValue<String>, RangedValue<Level>)> for Rules {
}
}
#[cfg(feature = "schemars")]
mod schema {
use crate::DEFAULT_LINT_REGISTRY;
use red_knot_python_semantic::lint::Level;
use schemars::gen::SchemaGenerator;
use schemars::schema::{
InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SubschemaValidation,
};
use schemars::JsonSchema;
pub(super) struct Rules;
impl JsonSchema for Rules {
fn schema_name() -> String {
"Rules".to_string()
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let registry = &*DEFAULT_LINT_REGISTRY;
let level_schema = gen.subschema_for::<Level>();
let properties: schemars::Map<String, Schema> = registry
.lints()
.iter()
.map(|lint| {
(
lint.name().to_string(),
Schema::Object(SchemaObject {
metadata: Some(Box::new(Metadata {
title: Some(lint.summary().to_string()),
description: Some(lint.documentation()),
deprecated: lint.status.is_deprecated(),
default: Some(lint.default_level.to_string().into()),
..Metadata::default()
})),
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![level_schema.clone()]),
..Default::default()
})),
..Default::default()
}),
)
})
.collect();
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties,
// Allow unknown rules: Red Knot will warn about them.
// It gives a better experience when using an older Red Knot version because
// the schema will not deny rules that have been removed in newer versions.
additional_properties: Some(Box::new(level_schema)),
..ObjectValidation::default()
})),
..Default::default()
})
}
}
}
#[derive(Error, Debug)]
pub enum KnotTomlError {
#[error(transparent)]

View File

@@ -1,8 +1,9 @@
use crate::combine::Combine;
use crate::Db;
use ruff_db::system::{System, SystemPath, SystemPathBuf};
use ruff_macros::Combine;
use ruff_text_size::{TextRange, TextSize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer};
use std::cell::RefCell;
use std::cmp::Ordering;
use std::fmt;
@@ -70,15 +71,19 @@ impl Drop for ValueSourceGuard {
///
/// This ensures that two resolved configurations are identical even if the position of a value has changed
/// or if the values were loaded from different sources.
#[derive(Clone)]
#[derive(Clone, serde::Serialize)]
#[serde(transparent)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct RangedValue<T> {
value: T,
#[serde(skip)]
source: ValueSource,
/// The byte range of `value` in `source`.
///
/// Can be `None` because not all sources support a range.
/// For example, arguments provided on the CLI won't have a range attached.
#[serde(skip)]
range: Option<TextRange>,
}
@@ -266,18 +271,6 @@ where
}
}
impl<T> Serialize for RangedValue<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value.serialize(serializer)
}
}
/// A possibly relative path in a configuration file.
///
/// Relative paths in configuration files or from CLI options
@@ -286,9 +279,19 @@ where
/// * CLI: The path is relative to the current working directory
/// * Configuration file: The path is relative to the project's root.
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Combine,
)]
#[serde(transparent)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct RelativePathBuf(RangedValue<SystemPathBuf>);
impl RelativePathBuf {
@@ -325,13 +328,3 @@ impl RelativePathBuf {
SystemPath::absolute(&self.0, relative_to)
}
}
impl Combine for RelativePathBuf {
fn combine(self, other: Self) -> Self {
Self(self.0.combine(other.0))
}
fn combine_with(&mut self, other: Self) {
self.0.combine_with(other.0);
}
}

View File

@@ -36,6 +36,7 @@ thiserror = { workspace = true }
tracing = { workspace = true }
rustc-hash = { workspace = true }
hashbrown = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
smallvec = { workspace = true }
static_assertions = { workspace = true }

View File

@@ -232,6 +232,36 @@ reveal_type(c_instance.y) # revealed: int
reveal_type(c_instance.z) # revealed: int
```
#### Attributes defined in multi-target assignments
```py
class C:
def __init__(self) -> None:
self.a = self.b = 1
c_instance = C()
reveal_type(c_instance.a) # revealed: Unknown | Literal[1]
reveal_type(c_instance.b) # revealed: Unknown | Literal[1]
```
#### Augmented assignments
```py
class Weird:
def __iadd__(self, other: None) -> str:
return "a"
class C:
def __init__(self) -> None:
self.w = Weird()
self.w += None
# TODO: Mypy and pyright do not support this, but it would be great if we could
# infer `Unknown | str` or at least `Unknown | Weird | str` here.
reveal_type(C().w) # revealed: Unknown | Weird
```
#### Attributes defined in tuple unpackings
```py
@@ -253,19 +283,24 @@ reveal_type(c_instance.b1) # revealed: Unknown | Literal["a"]
reveal_type(c_instance.c1) # revealed: Unknown | int
reveal_type(c_instance.d1) # revealed: Unknown | str
# TODO: This should be supported (no error; type should be: `Unknown | Literal[1]`)
# error: [unresolved-attribute]
reveal_type(c_instance.a2) # revealed: Unknown
reveal_type(c_instance.a2) # revealed: Unknown | Literal[1]
# TODO: This should be supported (no error; type should be: `Unknown | Literal["a"]`)
# error: [unresolved-attribute]
reveal_type(c_instance.b2) # revealed: Unknown
reveal_type(c_instance.b2) # revealed: Unknown | Literal["a"]
# TODO: Similar for these two (should be `Unknown | int` and `Unknown | str`, respectively)
# error: [unresolved-attribute]
reveal_type(c_instance.c2) # revealed: Unknown
# error: [unresolved-attribute]
reveal_type(c_instance.d2) # revealed: Unknown
reveal_type(c_instance.c2) # revealed: Unknown | int
reveal_type(c_instance.d2) # revealed: Unknown | str
```
#### Starred assignments
```py
class C:
def __init__(self) -> None:
self.a, *self.b = (1, 2, 3)
c_instance = C()
reveal_type(c_instance.a) # revealed: Unknown | Literal[1]
reveal_type(c_instance.b) # revealed: Unknown | @Todo(starred unpacking)
```
#### Attributes defined in for-loop (unpacking)
@@ -287,6 +322,8 @@ class TupleIterable:
def __iter__(self) -> TupleIterator:
return TupleIterator()
class NonIterable: ...
class C:
def __init__(self):
for self.x in IntIterable():
@@ -295,14 +332,54 @@ class C:
for _, self.y in TupleIterable():
pass
# TODO: Pyright fully supports these, mypy detects the presence of the attributes,
# but infers type `Any` for both of them. We should infer `int` and `str` here:
# TODO: We should emit a diagnostic here
for self.z in NonIterable():
pass
# error: [unresolved-attribute]
reveal_type(C().x) # revealed: Unknown
reveal_type(C().x) # revealed: Unknown | int
reveal_type(C().y) # revealed: Unknown | str
```
#### Attributes defined in `with` statements
```py
class ContextManager:
def __enter__(self) -> int | None: ...
def __exit__(self, exc_type, exc_value, traceback) -> None: ...
class C:
def __init__(self) -> None:
with ContextManager() as self.x:
pass
c_instance = C()
# TODO: Should be `Unknown | int | None`
# error: [unresolved-attribute]
reveal_type(C().y) # revealed: Unknown
reveal_type(c_instance.x) # revealed: Unknown
```
#### Attributes defined in comprehensions
```py
class IntIterator:
def __next__(self) -> int:
return 1
class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
class C:
def __init__(self) -> None:
[... for self.a in IntIterable()]
c_instance = C()
# TODO: Should be `Unknown | int`
# error: [unresolved-attribute]
reveal_type(c_instance.a) # revealed: Unknown
```
#### Conditionally declared / bound attributes
@@ -443,6 +520,15 @@ class C:
reveal_type(C().x) # revealed: str
```
#### Diagnostics are reported for the right-hand side of attribute assignments
```py
class C:
def __init__(self) -> None:
# error: [too-many-positional-arguments]
self.x: int = len(1, 2, 3)
```
### Pure class variables (`ClassVar`)
#### Annotated with `ClassVar` type qualifier

View File

@@ -219,7 +219,11 @@ import package
reveal_type(package.foo.X) # revealed: Unknown
```
## In the src-root
## Relative imports at the top of a search path
Relative imports at the top of a search path result in a runtime error:
`ImportError: attempted relative import with no known parent package`. That's why Red Knot should
disallow them.
`parser.py`:
@@ -230,21 +234,5 @@ X: int = 42
`__main__.py`:
```py
from .parser import X
reveal_type(X) # revealed: int
```
## Beyond the src-root
`parser.py`:
```py
X: int = 42
```
`__main__.py`:
```py
from ..parser import X # error: [unresolved-import]
from .parser import X # error: [unresolved-import]
```

View File

@@ -132,6 +132,27 @@ static_assert(not is_disjoint_from(Intersection[X, Z], Y))
static_assert(not is_disjoint_from(Intersection[Y, Z], X))
```
## Negation / complement
The complement of a type `T` is disjoint from `T`. In fact, it is disjoint from every subtype of
`T`:
```py
from knot_extensions import Not, Intersection, is_disjoint_from, static_assert
class T: ...
class S(T): ...
static_assert(is_disjoint_from(Not[T], T))
static_assert(is_disjoint_from(Not[T], S))
static_assert(is_disjoint_from(Intersection[T, Any], Not[T]))
static_assert(is_disjoint_from(Not[T], Intersection[T, Any]))
static_assert(is_disjoint_from(Intersection[S, Any], Not[T]))
static_assert(is_disjoint_from(Not[T], Intersection[S, Any]))
```
## Special types
### `Never`
@@ -244,7 +265,7 @@ static_assert(not is_disjoint_from(TypeOf[f], object))
### `AlwaysTruthy` and `AlwaysFalsy`
```py
from knot_extensions import AlwaysFalsy, AlwaysTruthy, is_disjoint_from, static_assert
from knot_extensions import AlwaysFalsy, AlwaysTruthy, Intersection, Not, is_disjoint_from, static_assert
from typing import Literal
static_assert(is_disjoint_from(None, AlwaysTruthy))
@@ -256,6 +277,14 @@ static_assert(not is_disjoint_from(str, AlwaysTruthy))
static_assert(is_disjoint_from(Literal[1, 2], AlwaysFalsy))
static_assert(not is_disjoint_from(Literal[0, 1], AlwaysTruthy))
type Truthy = Not[AlwaysFalsy]
type Falsy = Not[AlwaysTruthy]
type AmbiguousTruthiness = Intersection[Truthy, Falsy]
static_assert(is_disjoint_from(AlwaysTruthy, AmbiguousTruthiness))
static_assert(is_disjoint_from(AlwaysFalsy, AmbiguousTruthiness))
```
### Instance types versus `type[T]` types

View File

@@ -1,6 +1,8 @@
use core::fmt;
use itertools::Itertools;
use ruff_db::diagnostic::{DiagnosticId, LintName, Severity};
use rustc_hash::FxHashMap;
use std::fmt::Formatter;
use std::hash::Hasher;
use thiserror::Error;
@@ -36,13 +38,20 @@ pub struct LintMetadata {
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Level {
/// # Ignore
///
/// The lint is disabled and should not run.
Ignore,
/// # Warn
///
/// The lint is enabled and diagnostic should have a warning severity.
Warn,
/// # Error
///
/// The lint is enabled and diagnostics have an error severity.
Error,
}
@@ -61,6 +70,16 @@ impl Level {
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Level::Ignore => f.write_str("ignore"),
Level::Warn => f.write_str("warn"),
Level::Error => f.write_str("error"),
}
}
}
impl TryFrom<Level> for Severity {
type Error = ();
@@ -84,9 +103,11 @@ impl LintMetadata {
/// Returns the documentation line by line with one leading space and all trailing whitespace removed.
pub fn documentation_lines(&self) -> impl Iterator<Item = &str> {
self.raw_documentation
.lines()
.map(|line| line.strip_prefix(' ').unwrap_or(line).trim_end())
self.raw_documentation.lines().map(|line| {
line.strip_prefix(char::is_whitespace)
.unwrap_or(line)
.trim_end()
})
}
/// Returns the documentation as a single string.
@@ -180,6 +201,10 @@ impl LintStatus {
pub const fn is_removed(&self) -> bool {
matches!(self, LintStatus::Removed { .. })
}
pub const fn is_deprecated(&self) -> bool {
matches!(self, LintStatus::Deprecated { .. })
}
}
/// Declares a lint rule with the given metadata.
@@ -223,7 +248,7 @@ macro_rules! declare_lint {
$vis static $name: $crate::lint::LintMetadata = $crate::lint::LintMetadata {
name: ruff_db::diagnostic::LintName::of(ruff_macros::kebab_case!($name)),
summary: $summary,
raw_documentation: concat!($($doc,)+ "\n"),
raw_documentation: concat!($($doc, '\n',)+),
status: $status,
file: file!(),
line: line!(),

View File

@@ -11,6 +11,7 @@ pub enum PythonPlatform {
/// Do not make any assumptions about the target platform.
#[default]
All,
/// Assume a specific target platform like `linux`, `darwin` or `win32`.
///
/// We use a string (instead of individual enum variants), as the set of possible platforms
@@ -28,3 +29,77 @@ impl Display for PythonPlatform {
}
}
}
#[cfg(feature = "schemars")]
mod schema {
use crate::PythonPlatform;
use schemars::_serde_json::Value;
use schemars::gen::SchemaGenerator;
use schemars::schema::{Metadata, Schema, SchemaObject, SubschemaValidation};
use schemars::JsonSchema;
impl JsonSchema for PythonPlatform {
fn schema_name() -> String {
"PythonPlatform".to_string()
}
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
// Hard code some well known values, but allow any other string as well.
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(vec![
Schema::Object(SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
..SchemaObject::default()
}),
// Promote well-known values for better auto-completion.
// Using `const` over `enumValues` as recommended [here](https://github.com/SchemaStore/schemastore/blob/master/CONTRIBUTING.md#documenting-enums).
Schema::Object(SchemaObject {
const_value: Some(Value::String("all".to_string())),
metadata: Some(Box::new(Metadata {
description: Some(
"Do not make any assumptions about the target platform."
.to_string(),
),
..Metadata::default()
})),
..SchemaObject::default()
}),
Schema::Object(SchemaObject {
const_value: Some(Value::String("darwin".to_string())),
metadata: Some(Box::new(Metadata {
description: Some("Darwin".to_string()),
..Metadata::default()
})),
..SchemaObject::default()
}),
Schema::Object(SchemaObject {
const_value: Some(Value::String("linux".to_string())),
metadata: Some(Box::new(Metadata {
description: Some("Linux".to_string()),
..Metadata::default()
})),
..SchemaObject::default()
}),
Schema::Object(SchemaObject {
const_value: Some(Value::String("win32".to_string())),
metadata: Some(Box::new(Metadata {
description: Some("Windows".to_string()),
..Metadata::default()
})),
..SchemaObject::default()
}),
]),
..SubschemaValidation::default()
})),
..SchemaObject::default()
})
}
}
}

View File

@@ -31,6 +31,20 @@ impl PythonVersion {
minor: 13,
};
pub fn iter() -> impl Iterator<Item = PythonVersion> {
[
PythonVersion::PY37,
PythonVersion::PY38,
PythonVersion::PY39,
PythonVersion::PY310,
PythonVersion::PY311,
PythonVersion::PY312,
PythonVersion::PY313,
]
.iter()
.copied()
}
pub fn free_threaded_build_available(self) -> bool {
self >= PythonVersion::PY313
}
@@ -69,40 +83,86 @@ impl fmt::Display for PythonVersion {
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PythonVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let as_str = String::deserialize(deserializer)?;
mod serde {
use crate::PythonVersion;
if let Some((major, minor)) = as_str.split_once('.') {
let major = major
.parse()
.map_err(|err| serde::de::Error::custom(format!("invalid major version: {err}")))?;
let minor = minor
.parse()
.map_err(|err| serde::de::Error::custom(format!("invalid minor version: {err}")))?;
impl<'de> serde::Deserialize<'de> for PythonVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let as_str = String::deserialize(deserializer)?;
Ok((major, minor).into())
} else {
let major = as_str.parse().map_err(|err| {
serde::de::Error::custom(format!(
"invalid python-version: {err}, expected: `major.minor`"
))
})?;
if let Some((major, minor)) = as_str.split_once('.') {
let major = major.parse().map_err(|err| {
serde::de::Error::custom(format!("invalid major version: {err}"))
})?;
let minor = minor.parse().map_err(|err| {
serde::de::Error::custom(format!("invalid minor version: {err}"))
})?;
Ok((major, 0).into())
Ok((major, minor).into())
} else {
let major = as_str.parse().map_err(|err| {
serde::de::Error::custom(format!(
"invalid python-version: {err}, expected: `major.minor`"
))
})?;
Ok((major, 0).into())
}
}
}
impl serde::Serialize for PythonVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PythonVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
#[cfg(feature = "schemars")]
mod schemars {
use super::PythonVersion;
use schemars::schema::{Metadata, Schema, SchemaObject, SubschemaValidation};
use schemars::JsonSchema;
use schemars::_serde_json::Value;
impl JsonSchema for PythonVersion {
fn schema_name() -> String {
"PythonVersion".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
let sub_schemas = std::iter::once(Schema::Object(SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
string: Some(Box::new(schemars::schema::StringValidation {
pattern: Some(r"^\d+\.\d+$".to_string()),
..Default::default()
})),
..Default::default()
}))
.chain(Self::iter().map(|v| {
Schema::Object(SchemaObject {
const_value: Some(Value::String(v.to_string())),
metadata: Some(Box::new(Metadata {
description: Some(format!("Python {v}")),
..Metadata::default()
})),
..SchemaObject::default()
})
}));
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(sub_schemas.collect()),
..Default::default()
})),
..SchemaObject::default()
})
}
}
}

View File

@@ -1,4 +1,7 @@
use crate::semantic_index::expression::Expression;
use crate::{
semantic_index::{ast_ids::ScopedExpressionId, expression::Expression},
unpack::Unpack,
};
use ruff_python_ast::name::Name;
@@ -14,6 +17,17 @@ pub(crate) enum AttributeAssignment<'db> {
/// An attribute assignment without a type annotation, e.g. `self.x = <value>`.
Unannotated { value: Expression<'db> },
/// An attribute assignment where the right-hand side is an iterable, for example
/// `for self.x in <iterable>`.
Iterable { iterable: Expression<'db> },
/// An attribute assignment where the left-hand side is an unpacking expression,
/// e.g. `self.x, self.y = <value>`.
Unpack {
attribute_expression_id: ScopedExpressionId,
unpack: Unpack<'db>,
},
}
pub(crate) type AttributeAssignments<'db> = FxHashMap<Name, Vec<AttributeAssignment<'db>>>;

View File

@@ -6,9 +6,9 @@ use rustc_hash::{FxHashMap, FxHashSet};
use ruff_db::files::File;
use ruff_db::parsed::ParsedModule;
use ruff_index::IndexVec;
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
use ruff_python_ast::{self as ast, ExprContext};
use crate::ast_node_ref::AstNodeRef;
use crate::module_name::ModuleName;
@@ -1231,6 +1231,20 @@ where
unpack: None,
first: false,
}),
ast::Expr::Attribute(ast::ExprAttribute {
value: object,
attr,
..
}) => {
self.register_attribute_assignment(
object,
attr,
AttributeAssignment::Iterable {
iterable: iter_expr,
},
);
None
}
_ => None,
};
@@ -1459,7 +1473,7 @@ where
fn visit_expr(&mut self, expr: &'ast ast::Expr) {
self.scopes_by_expression
.insert(expr.into(), self.current_scope());
self.current_ast_ids().record_expression(expr);
let expression_id = self.current_ast_ids().record_expression(expr);
match expr {
ast::Expr::Name(name_node @ ast::ExprName { id, ctx, .. }) => {
@@ -1718,6 +1732,35 @@ where
self.simplify_visibility_constraints(pre_op);
}
ast::Expr::Attribute(ast::ExprAttribute {
value: object,
attr,
ctx: ExprContext::Store,
range: _,
}) => {
if let Some(
CurrentAssignment::Assign {
unpack: Some(unpack),
..
}
| CurrentAssignment::For {
unpack: Some(unpack),
..
},
) = self.current_assignment()
{
self.register_attribute_assignment(
object,
attr,
AttributeAssignment::Unpack {
attribute_expression_id: expression_id,
unpack,
},
);
}
walk_expr(self, expr);
}
_ => {
walk_expr(self, expr);
}

View File

@@ -40,6 +40,7 @@ use crate::types::call::{
};
use crate::types::class_base::ClassBase;
use crate::types::diagnostic::INVALID_TYPE_FORM;
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};
use crate::types::narrow::narrowing_constraint;
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
@@ -1260,19 +1261,42 @@ impl<'db> Type<'db> {
.iter()
.all(|e| e.is_disjoint_from(db, other)),
(Type::Intersection(intersection), other)
| (other, Type::Intersection(intersection)) => {
if intersection
(Type::Intersection(inter_left), Type::Intersection(inter_right)) => {
// We explicitly make this case a symmetric version of the case below, as there
// are some type pairs like `Any & T` and `~T` that would otherwise lead to non-
// symmetric results.
inter_left
.positive(db)
.iter()
.any(|p| p.is_disjoint_from(db, other))
{
true
} else {
// TODO we can do better here. For example:
// X & ~Literal[1] is disjoint from Literal[1]
false
}
|| inter_right
.positive(db)
.iter()
.any(|p| p.is_disjoint_from(db, self))
|| inter_left.negative(db).iter().any(|n| {
other.is_subtype_of(db, *n)
&& self.is_fully_static(db)
&& other.is_fully_static(db)
})
|| inter_right.negative(db).iter().any(|n| {
self.is_subtype_of(db, *n)
&& self.is_fully_static(db)
&& other.is_fully_static(db)
})
}
(Type::Intersection(intersection), t) | (t, Type::Intersection(intersection)) => {
// TODO: There are certainly more cases that could be handled here. For example,
// it is possible that both A and B overlap with C, but the intersection A & B
// does not overlap with C.
intersection
.positive(db)
.iter()
.any(|p| p.is_disjoint_from(db, t))
|| intersection.negative(db).iter().any(|n| {
t.is_subtype_of(db, *n)
&& self.is_fully_static(db)
&& other.is_fully_static(db)
})
}
// any single-valued type is disjoint from another single-valued type
@@ -4231,6 +4255,32 @@ impl<'db> Class<'db> {
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
}
AttributeAssignment::Iterable { iterable } => {
// We found an attribute assignment like:
//
// for self.name in <iterable>:
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
let iterable_ty = infer_expression_type(db, *iterable);
let inferred_ty = iterable_ty.iterate(db).unwrap_without_diagnostic();
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
}
AttributeAssignment::Unpack {
attribute_expression_id,
unpack,
} => {
// We found an unpacking assignment like:
//
// .., self.name, .. = <value>
// (.., self.name, ..) = <value>
// [.., self.name, ..] = <value>
let inferred_ty =
infer_unpack_types(db, *unpack).expression_type(*attribute_expression_id);
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
}
}
}

View File

@@ -200,7 +200,7 @@ pub(crate) fn infer_expression_types<'db>(
/// type of the variables involved in this unpacking along with any violations that are detected
/// during this unpacking.
#[salsa::tracked(return_ref)]
fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
let file = unpack.file(db);
let _span =
tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), file=%file.path(db))
@@ -2085,7 +2085,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
let name_ast_id = name.scoped_expression_id(self.db(), self.scope());
unpacked.get(name_ast_id).unwrap_or(Type::unknown())
unpacked.expression_type(name_ast_id)
}
TargetKind::Name => {
if self.in_stub() && value.is_ellipsis_literal_expr() {
@@ -2356,7 +2356,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.context.extend(unpacked);
}
let name_ast_id = name.scoped_expression_id(self.db(), self.scope());
unpacked.get(name_ast_id).unwrap_or(Type::unknown())
unpacked.expression_type(name_ast_id)
}
TargetKind::Name => iterable_ty
.iterate(self.db())
@@ -2512,30 +2512,19 @@ impl<'db> TypeInferenceBuilder<'db> {
let module = file_to_module(self.db(), self.file())
.ok_or(ModuleNameResolutionError::UnknownCurrentModule)?;
let mut level = level.get();
if module.kind().is_package() {
level = level.saturating_sub(1);
}
let mut module_name = module.name().clone();
let tail = tail
.map(|tail| ModuleName::new(tail).ok_or(ModuleNameResolutionError::InvalidSyntax))
.transpose()?;
for remaining_dots in (0..level).rev() {
if let Some(parent) = module_name.parent() {
module_name = parent;
} else if remaining_dots == 0 {
// If we reached a search path root and this was the last dot return the tail if any.
// If there's no tail, then we have a relative import that's too deep.
return tail.ok_or(ModuleNameResolutionError::TooManyDots);
} else {
// We're at a search path root. This is a too deep relative import if there's more than
// one dot remaining.
return Err(ModuleNameResolutionError::TooManyDots);
}
}
let mut module_name = module
.name()
.ancestors()
.nth(level as usize)
.ok_or(ModuleNameResolutionError::TooManyDots)?;
if let Some(tail) = tail {
let tail = ModuleName::new(tail).ok_or(ModuleNameResolutionError::InvalidSyntax)?;
module_name.extend(&tail);
}

View File

@@ -72,11 +72,9 @@ impl<'db> Unpacker<'db> {
value_ty: Type<'db>,
) {
match target {
ast::Expr::Name(target_name) => {
self.targets.insert(
target_name.scoped_expression_id(self.db(), self.scope),
value_ty,
);
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
self.targets
.insert(target.scoped_expression_id(self.db(), self.scope), value_ty);
}
ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
self.unpack_inner(value, value_expr, value_ty);
@@ -270,8 +268,14 @@ pub(crate) struct UnpackResult<'db> {
}
impl<'db> UnpackResult<'db> {
pub(crate) fn get(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
self.targets.get(&expr_id).copied()
/// Returns the inferred type for a given sub-expression of the left-hand side target
/// of an unpacking assignment.
///
/// Panics if a scoped expression ID is passed in that does not correspond to a sub-
/// expression of the target.
#[track_caller]
pub(crate) fn expression_type(&self, expr_id: ScopedExpressionId) -> Type<'db> {
self.targets[&expr_id]
}
}

View File

@@ -3,9 +3,9 @@
#![cfg(not(target_family = "wasm"))]
use regex::escape;
use std::fs;
use std::process::Command;
use std::str;
use std::{fs, path::Path};
use anyhow::Result;
use assert_fs::fixture::{ChildPath, FileTouch, PathChild};
@@ -2236,3 +2236,149 @@ def func(t: _T) -> _T:
"
);
}
/// Test that we do not rename two different type parameters to the same name
/// in one execution of Ruff (autofixing this to `class Foo[T, T]: ...` would
/// introduce invalid syntax)
#[test]
fn type_parameter_rename_isolation() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "UP049"])
.args(["--stdin-filename", "test.py"])
.arg("--unsafe-fixes")
.arg("--fix")
.arg("--preview")
.arg("--target-version=py312")
.arg("-")
.pass_stdin(
r#"
class Foo[_T, __T]:
pass
"#
),
@r"
success: false
exit_code: 1
----- stdout -----
class Foo[T, __T]:
pass
----- stderr -----
test.py:2:14: UP049 Generic class uses private type parameters
Found 2 errors (1 fixed, 1 remaining).
"
);
}
#[test]
fn a005_module_shadowing_strict() -> Result<()> {
fn create_module(path: &Path) -> Result<()> {
fs::create_dir(path)?;
fs::File::create(path.join("__init__.py"))?;
Ok(())
}
// construct a directory tree with this structure:
// .
// ├── abc
// │   └── __init__.py
// ├── collections
// │   ├── __init__.py
// │   ├── abc
// │   │   └── __init__.py
// │   └── foobar
// │   └── __init__.py
// ├── foobar
// │   ├── __init__.py
// │   ├── abc
// │   │   └── __init__.py
// │   └── collections
// │   ├── __init__.py
// │   ├── abc
// │   │   └── __init__.py
// │   └── foobar
// │   └── __init__.py
// ├── ruff.toml
// └── urlparse
// └── __init__.py
let tempdir = TempDir::new()?;
let foobar = tempdir.path().join("foobar");
create_module(&foobar)?;
for base in [&tempdir.path().into(), &foobar] {
for dir in ["abc", "collections"] {
create_module(&base.join(dir))?;
}
create_module(&base.join("collections").join("abc"))?;
create_module(&base.join("collections").join("foobar"))?;
}
create_module(&tempdir.path().join("urlparse"))?;
// also create a ruff.toml to mark the project root
fs::File::create(tempdir.path().join("ruff.toml"))?;
insta::with_settings!({
filters => vec![(r"\\", "/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "A005"])
.current_dir(tempdir.path()),
@r"
success: false
exit_code: 1
----- stdout -----
abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
foobar/collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
Found 6 errors.
----- stderr -----
");
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "A005"])
.current_dir(tempdir.path()),
@r"
success: false
exit_code: 1
----- stdout -----
abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
foobar/collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
Found 6 errors.
----- stderr -----
");
// TODO(brent) Default should currently match the strict version, but after the next minor
// release it will match the non-strict version directly above
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "A005"])
.current_dir(tempdir.path()),
@r"
success: false
exit_code: 1
----- stdout -----
abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
foobar/collections/__init__.py:1:1: A005 Module `collections` shadows a Python standard-library module
foobar/collections/abc/__init__.py:1:1: A005 Module `abc` shadows a Python standard-library module
Found 6 errors.
----- stderr -----
");
});
Ok(())
}

View File

@@ -30,6 +30,7 @@ glob = { workspace = true }
ignore = { workspace = true, optional = true }
matchit = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
path-slash = { workspace = true }
thiserror = { workspace = true }

View File

@@ -471,7 +471,13 @@ impl ToOwned for SystemPath {
/// The path is guaranteed to be valid UTF-8.
#[repr(transparent)]
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
pub struct SystemPathBuf(Utf8PathBuf);
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
impl SystemPathBuf {
pub fn new() -> Self {
@@ -658,27 +664,6 @@ impl ruff_cache::CacheKey for SystemPathBuf {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SystemPath {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SystemPathBuf {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SystemPathBuf {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Utf8PathBuf::deserialize(deserializer).map(SystemPathBuf)
}
}
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
#[repr(transparent)]
pub struct SystemVirtualPath(str);

View File

@@ -11,6 +11,7 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
red_knot_project = { workspace = true, features = ["schemars"] }
ruff = { workspace = true }
ruff_diagnostics = { workspace = true }
ruff_formatter = { workspace = true }

View File

@@ -2,7 +2,7 @@
use anyhow::Result;
use crate::{generate_cli_help, generate_docs, generate_json_schema};
use crate::{generate_cli_help, generate_docs, generate_json_schema, generate_knot_schema};
pub(crate) const REGENERATE_ALL_COMMAND: &str = "cargo dev generate-all";
@@ -33,6 +33,7 @@ impl Mode {
pub(crate) fn main(args: &Args) -> Result<()> {
generate_json_schema::main(&generate_json_schema::Args { mode: args.mode })?;
generate_knot_schema::main(&generate_knot_schema::Args { mode: args.mode })?;
generate_cli_help::main(&generate_cli_help::Args { mode: args.mode })?;
generate_docs::main(&generate_docs::Args {
dry_run: args.mode.is_dry_run(),

View File

@@ -0,0 +1,72 @@
#![allow(clippy::print_stdout, clippy::print_stderr)]
use std::fs;
use std::path::PathBuf;
use anyhow::{bail, Result};
use pretty_assertions::StrComparison;
use schemars::schema_for;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
use crate::ROOT_DIR;
use red_knot_project::metadata::options::Options;
#[derive(clap::Args)]
pub(crate) struct Args {
/// Write the generated table to stdout (rather than to `knot.schema.json`).
#[arg(long, default_value_t, value_enum)]
pub(crate) mode: Mode,
}
pub(crate) fn main(args: &Args) -> Result<()> {
let schema = schema_for!(Options);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
let filename = "knot.schema.json";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);
match args.mode {
Mode::DryRun => {
println!("{schema_string}");
}
Mode::Check => {
let current = fs::read_to_string(schema_path)?;
if current == schema_string {
println!("Up-to-date: {filename}");
} else {
let comparison = StrComparison::new(&current, &schema_string);
bail!("{filename} changed, please run `{REGENERATE_ALL_COMMAND}`:\n{comparison}");
}
}
Mode::Write => {
let current = fs::read_to_string(&schema_path)?;
if current == schema_string {
println!("Up-to-date: {filename}");
} else {
println!("Updating: {filename}");
fs::write(schema_path, schema_string.as_bytes())?;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use std::env;
use crate::generate_all::Mode;
use super::{main, Args};
#[test]
fn test_generate_json_schema() -> Result<()> {
let mode = if env::var("KNOT_UPDATE_SCHEMA").as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode })
}
}

View File

@@ -13,6 +13,7 @@ mod generate_all;
mod generate_cli_help;
mod generate_docs;
mod generate_json_schema;
mod generate_knot_schema;
mod generate_options;
mod generate_rules_table;
mod print_ast;
@@ -39,6 +40,8 @@ enum Command {
GenerateAll(generate_all::Args),
/// Generate JSON schema for the TOML configuration file.
GenerateJSONSchema(generate_json_schema::Args),
/// Generate JSON schema for the Red Knot TOML configuration file.
GenerateKnotSchema(generate_knot_schema::Args),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable,
/// Generate a Markdown-compatible listing of configuration options.
@@ -83,6 +86,7 @@ fn main() -> Result<ExitCode> {
match command {
Command::GenerateAll(args) => generate_all::main(&args)?,
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
Command::GenerateKnotSchema(args) => generate_knot_schema::main(&args)?,
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
Command::GenerateOptions => println!("{}", generate_options::generate()),
Command::GenerateCliHelp(args) => generate_cli_help::main(&args)?,

View File

@@ -75,15 +75,10 @@ from airflow.secrets.local_filesystem import LocalFilesystemBackend, load_connec
from airflow.security.permissions import RESOURCE_DATASET
from airflow.sensors.base_sensor_operator import BaseSensorOperator
from airflow.sensors.date_time_sensor import DateTimeSensor
from airflow.sensors.external_task import (
ExternalTaskSensorLink as ExternalTaskSensorLinkFromExternalTask,
)
from airflow.sensors.external_task_sensor import (
ExternalTaskMarker,
ExternalTaskSensor,
)
from airflow.sensors.external_task_sensor import (
ExternalTaskSensorLink as ExternalTaskSensorLinkFromExternalTaskSensor,
ExternalTaskSensorLink,
)
from airflow.sensors.time_delta_sensor import TimeDeltaSensor
from airflow.timetables.datasets import DatasetOrTimeSchedule
@@ -249,11 +244,13 @@ BaseSensorOperator()
DateTimeSensor()
# airflow.sensors.external_task
ExternalTaskSensorLinkFromExternalTask()
# airflow.sensors.external_task_sensor
ExternalTaskSensorLink()
ExternalTaskMarker()
ExternalTaskSensor()
# airflow.sensors.external_task_sensor
ExternalTaskMarkerFromExternalTaskSensor()
ExternalTaskSensorFromExternalTaskSensor()
ExternalTaskSensorLinkFromExternalTaskSensor()
# airflow.sensors.time_delta_sensor

View File

@@ -0,0 +1,28 @@
##### https://github.com/astral-sh/ruff/issues/15809
### Errors
def overshadowed_list():
list = ...
list(map(lambda x: x, []))
### No errors
dict(map(lambda k: (k,), a))
dict(map(lambda k: (k, v, 0), a))
dict(map(lambda k: [k], a))
dict(map(lambda k: [k, v, 0], a))
dict(map(lambda k: {k, v}, a))
dict(map(lambda k: {k: 0, v: 1}, a))
a = [(1, 2), (3, 4)]
map(lambda x: [*x, 10], *a)
map(lambda x: [*x, 10], *a, *b)
map(lambda x: [*x, 10], a, *b)
map(lambda x: x + 10, (a := []))
list(map(lambda x: x + 10, (a := [])))
set(map(lambda x: x + 10, (a := [])))
dict(map(lambda x: (x, 10), (a := [])))

View File

@@ -70,6 +70,32 @@ foo({**foo, **{"bar": True}}) # PIE800
,
})
{
"data": [],
** # Foo
( # Comment
{ "a": b,
# Comment
}
) ,
c: 9,
}
# https://github.com/astral-sh/ruff/issues/15997
{"a": [], **{},}
{"a": [], **({}),}
{"a": [], **{}, 6: 3}
{"a": [], **({}), 6: 3}
{"a": [], **{
# Comment
}, 6: 3}
{"a": [], **({
# Comment
}), 6: 3}
{**foo, "bar": True } # OK

View File

@@ -2,6 +2,8 @@
# Positive cases
###
a_dict = {}
# SIM401 (pattern-1)
if key in a_dict:
var = a_dict[key]
@@ -26,6 +28,8 @@ if keys[idx] in a_dict:
else:
var = "default"
dicts = {"key": a_dict}
# SIM401 (complex expression in dict)
if key in dicts[idx]:
var = dicts[idx][key]
@@ -115,6 +119,28 @@ elif key in a_dict:
else:
vars[idx] = "default"
class NotADictionary:
def __init__(self):
self._dict = {}
def __getitem__(self, key):
return self._dict[key]
def __setitem__(self, key, value):
self._dict[key] = value
def __iter__(self):
return self._dict.__iter__()
not_dict = NotADictionary()
not_dict["key"] = "value"
# OK (type `NotADictionary` is not a known dictionary type)
if "key" in not_dict:
value = not_dict["key"]
else:
value = None
###
# Positive cases (preview)
###

View File

@@ -64,10 +64,42 @@ u''.strip('http://')
u''.lstrip('http://')
# PLE1310
b''.rstrip('http://')
b''.rstrip(b'http://')
# OK
''.strip('Hi')
# OK
''.strip()
### https://github.com/astral-sh/ruff/issues/15968
# Errors: Multiple backslashes
''.strip('\\b\\x09')
''.strip(r'\b\x09')
''.strip('\\\x5C')
# OK: Different types
b"".strip("//")
"".strip(b"//")
# OK: Escapes
'\\test'.strip('\\')
# OK: Extra/missing arguments
"".strip("//", foo)
b"".lstrip(b"//", foo = "bar")
"".rstrip()
# OK: Not literals
foo: str = ""; bar: bytes = b""
"".strip(foo)
b"".strip(bar)
# False negative
foo.rstrip("//")
bar.lstrip(b"//")
# OK: Not `.[lr]?strip`
"".mobius_strip("")

View File

@@ -113,3 +113,18 @@ PositiveList = TypeAliasType(
Annotated[T, Gt(0)], # preserved comment
], type_params=(T,)
)
T: TypeAlias = (
int
| str
)
T: TypeAlias = ( # comment0
# comment1
int # comment2
# comment3
| # comment4
# comment5
str # comment6
# comment7
) # comment8

View File

@@ -12,3 +12,13 @@ x: TypeAlias = tuple[
int, # preserved
float,
]
T: TypeAlias = ( # comment0
# comment1
int # comment2
# comment3
| # comment4
# comment5
str # comment6
# comment7
) # comment8

View File

@@ -54,3 +54,52 @@ def f[_](x: _) -> _: ...
def g[__](x: __) -> __: ...
def h[_T_](x: _T_) -> _T_: ...
def i[__T__](x: __T__) -> __T__: ...
# https://github.com/astral-sh/ruff/issues/16024
from typing import cast, Literal
class C[_0]: ...
class C[T, _T]: ...
class C[_T, T]: ...
class C[_T]:
v1 = cast(_T, ...)
v2 = cast('_T', ...)
v3 = cast("\u005fT", ...)
def _(self):
v1 = cast(_T, ...)
v2 = cast('_T', ...)
v3 = cast("\u005fT", ...)
class C[_T]:
v = cast('Literal[\'foo\'] | _T', ...)
## Name collision
class C[T]:
def f[_T](self): # No fix, collides with `T` from outer scope
v1 = cast(_T, ...)
v2 = cast('_T', ...)
# Unfixable as the new name collides with a variable visible from one of the inner scopes
class C[_T]:
T = 42
v1 = cast(_T, ...)
v2 = cast('_T', ...)
# Unfixable as the new name collides with a variable visible from one of the inner scopes
class C[_T]:
def f[T](self):
v1 = cast(_T, ...)
v2 = cast('_T', ...)

View File

@@ -176,3 +176,22 @@ class Node:
_seen.add(self)
for other in self.connected:
other.recurse(_seen=_seen)
def foo():
_dummy_var = 42
def bar():
dummy_var = 43
print(_dummy_var)
def foo():
# Unfixable because both possible candidates for the new name are shadowed
# in the scope of one of the references to the variable
_dummy_var = 42
def bar():
dummy_var = 43
dummy_var_ = 44
print(_dummy_var)

View File

@@ -9,7 +9,7 @@ use crate::rules::{
};
/// Run lint rules over the [`Binding`]s.
pub(crate) fn bindings(checker: &mut Checker) {
pub(crate) fn bindings(checker: &Checker) {
if !checker.any_enabled(&[
Rule::AssignmentInAssert,
Rule::InvalidAllFormat,
@@ -48,22 +48,22 @@ pub(crate) fn bindings(checker: &mut Checker) {
pyflakes::fixes::remove_exception_handler_assignment(binding, checker.locator)
.map(Fix::safe_edit)
});
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::InvalidAllFormat) {
if let Some(diagnostic) = pylint::rules::invalid_all_format(binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::InvalidAllObject) {
if let Some(diagnostic) = pylint::rules::invalid_all_object(binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::NonAsciiName) {
if let Some(diagnostic) = pylint::rules::non_ascii_name(binding, checker.locator) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::UnconventionalImportAlias) {
@@ -72,61 +72,61 @@ pub(crate) fn bindings(checker: &mut Checker) {
binding,
&checker.settings.flake8_import_conventions.aliases,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
if let Some(diagnostic) =
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if !checker.source_type.is_stub() && checker.enabled(Rule::UnquotedTypeAlias) {
if let Some(diagnostics) =
flake8_type_checking::rules::unquoted_type_alias(checker, binding)
{
checker.diagnostics.extend(diagnostics);
checker.report_diagnostics(diagnostics);
}
}
if checker.enabled(Rule::UnsortedDunderSlots) {
if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::UsedDummyVariable) {
if let Some(diagnostic) = ruff::rules::used_dummy_variable(checker, binding, binding_id)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::AssignmentInAssert) {
if let Some(diagnostic) = ruff::rules::assignment_in_assert(checker, binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::PytestUnittestRaisesAssertion) {
if let Some(diagnostic) =
flake8_pytest_style::rules::unittest_raises_assertion_binding(checker, binding)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::ForLoopWrites) {
if let Some(diagnostic) = refurb::rules::for_loop_writes_binding(checker, binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CustomTypeVarForSelf) {
if let Some(diagnostic) =
flake8_pyi::rules::custom_type_var_instead_of_self(checker, binding)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::PrivateTypeParameter) {
if let Some(diagnostic) = pyupgrade::rules::private_type_parameter(checker, binding) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}

View File

@@ -5,7 +5,7 @@ use crate::codes::Rule;
use crate::rules::{flake8_simplify, pylint, refurb};
/// Run lint rules over a [`Comprehension`] syntax nodes.
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &mut Checker) {
pub(crate) fn comprehension(comprehension: &Comprehension, checker: &Checker) {
if checker.enabled(Rule::InDictKeys) {
flake8_simplify::rules::key_in_dict_comprehension(checker, comprehension);
}

View File

@@ -13,7 +13,7 @@ use crate::rules::{
};
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
pub(crate) fn deferred_scopes(checker: &mut Checker) {
pub(crate) fn deferred_scopes(checker: &Checker) {
if !checker.any_enabled(&[
Rule::AsyncioDanglingTask,
Rule::BadStaticmethodArgument,
@@ -85,12 +85,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
vec![]
};
let mut diagnostics: Vec<Diagnostic> = vec![];
for scope_id in checker.analyze.scopes.iter().rev().copied() {
let scope = &checker.semantic.scopes[scope_id];
if checker.enabled(Rule::UndefinedLocal) {
pyflakes::rules::undefined_local(checker, scope_id, scope, &mut diagnostics);
pyflakes::rules::undefined_local(checker, scope_id, scope);
}
if checker.enabled(Rule::GlobalVariableNotAssigned) {
@@ -112,7 +111,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
.map(|id| checker.semantic.reference(*id))
.all(ResolvedReference::is_load)
{
diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
@@ -146,7 +145,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if scope.kind.is_generator() {
continue;
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pylint::rules::RedefinedArgumentFromLocal {
name: name.to_string(),
},
@@ -186,7 +185,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
continue;
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::ImportShadowedByLoopVar {
name: name.to_string(),
row: checker.compute_source_row(shadowed.start()),
@@ -347,7 +346,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
diagnostic.set_fix(fix.clone());
}
diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -356,55 +355,47 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|| matches!(scope.kind, ScopeKind::Module | ScopeKind::Function(_))
{
if checker.enabled(Rule::UnusedPrivateTypeVar) {
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
flake8_pyi::rules::unused_private_type_var(checker, scope);
}
if checker.enabled(Rule::UnusedPrivateProtocol) {
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
flake8_pyi::rules::unused_private_protocol(checker, scope);
}
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
flake8_pyi::rules::unused_private_type_alias(checker, scope);
}
if checker.enabled(Rule::UnusedPrivateTypedDict) {
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
flake8_pyi::rules::unused_private_typed_dict(checker, scope);
}
}
if checker.enabled(Rule::AsyncioDanglingTask) {
ruff::rules::asyncio_dangling_binding(scope, &checker.semantic, &mut diagnostics);
ruff::rules::asyncio_dangling_binding(scope, checker);
}
if let Some(class_def) = scope.kind.as_class() {
if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_attribute_shadowing(
checker,
scope_id,
scope,
class_def,
&mut diagnostics,
checker, scope_id, scope, class_def,
);
}
if checker.enabled(Rule::FunctionCallInDataclassDefaultArgument) {
ruff::rules::function_call_in_dataclass_default(
checker,
class_def,
&mut diagnostics,
);
ruff::rules::function_call_in_dataclass_default(checker, class_def);
}
if checker.enabled(Rule::MutableClassDefault) {
ruff::rules::mutable_class_default(checker, class_def, &mut diagnostics);
ruff::rules::mutable_class_default(checker, class_def);
}
if checker.enabled(Rule::MutableDataclassDefault) {
ruff::rules::mutable_dataclass_default(checker, class_def, &mut diagnostics);
ruff::rules::mutable_dataclass_default(checker, class_def);
}
}
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Lambda(_)) {
if checker.enabled(Rule::UnusedVariable) {
pyflakes::rules::unused_variable(checker, scope, &mut diagnostics);
pyflakes::rules::unused_variable(checker, scope);
}
if checker.enabled(Rule::UnusedAnnotation) {
pyflakes::rules::unused_annotation(checker, scope, &mut diagnostics);
pyflakes::rules::unused_annotation(checker, scope);
}
if !checker.source_type.is_stub() {
@@ -415,11 +406,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
Rule::UnusedMethodArgument,
Rule::UnusedStaticMethodArgument,
]) {
flake8_unused_arguments::rules::unused_arguments(
checker,
scope,
&mut diagnostics,
);
flake8_unused_arguments::rules::unused_arguments(checker, scope);
}
}
}
@@ -428,11 +415,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if !checker.source_type.is_stub()
&& checker.enabled(Rule::RuntimeImportInTypeCheckingBlock)
{
flake8_type_checking::rules::runtime_import_in_type_checking_block(
checker,
scope,
&mut diagnostics,
);
flake8_type_checking::rules::runtime_import_in_type_checking_block(checker, scope);
}
if enforce_typing_only_imports {
let runtime_imports: Vec<&Binding> = checker
@@ -447,47 +430,45 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
checker,
scope,
&runtime_imports,
&mut diagnostics,
);
}
if checker.enabled(Rule::UnusedImport) {
pyflakes::rules::unused_import(checker, scope, &mut diagnostics);
pyflakes::rules::unused_import(checker, scope);
}
if checker.enabled(Rule::ImportPrivateName) {
pylint::rules::import_private_name(checker, scope, &mut diagnostics);
pylint::rules::import_private_name(checker, scope);
}
}
if scope.kind.is_function() {
if checker.enabled(Rule::NoSelfUse) {
pylint::rules::no_self_use(checker, scope_id, scope, &mut diagnostics);
pylint::rules::no_self_use(checker, scope_id, scope);
}
if checker.enabled(Rule::TooManyLocals) {
pylint::rules::too_many_locals(checker, scope, &mut diagnostics);
pylint::rules::too_many_locals(checker, scope);
}
if checker.enabled(Rule::SingledispatchMethod) {
pylint::rules::singledispatch_method(checker, scope, &mut diagnostics);
pylint::rules::singledispatch_method(checker, scope);
}
if checker.enabled(Rule::SingledispatchmethodFunction) {
pylint::rules::singledispatchmethod_function(checker, scope, &mut diagnostics);
pylint::rules::singledispatchmethod_function(checker, scope);
}
if checker.enabled(Rule::BadStaticmethodArgument) {
pylint::rules::bad_staticmethod_argument(checker, scope, &mut diagnostics);
pylint::rules::bad_staticmethod_argument(checker, scope);
}
if checker.any_enabled(&[
Rule::InvalidFirstArgumentNameForClassMethod,
Rule::InvalidFirstArgumentNameForMethod,
]) {
pep8_naming::rules::invalid_first_argument_name(checker, scope, &mut diagnostics);
pep8_naming::rules::invalid_first_argument_name(checker, scope);
}
}
}
checker.diagnostics.extend(diagnostics);
}

View File

@@ -139,13 +139,11 @@ pub(crate) fn definitions(checker: &mut Checker) {
&checker.semantic,
)
}) {
checker
.diagnostics
.extend(flake8_annotations::rules::definition(
checker,
definition,
*visibility,
));
checker.report_diagnostics(flake8_annotations::rules::definition(
checker,
definition,
*visibility,
));
}
overloaded_name =
flake8_annotations::helpers::overloaded_name(definition, &checker.semantic);

View File

@@ -8,7 +8,7 @@ use crate::rules::{
};
/// Run lint rules over an [`ExceptHandler`] syntax node.
pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &mut Checker) {
pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &Checker) {
match except_handler {
ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
type_,
@@ -23,7 +23,7 @@ pub(crate) fn except_handler(except_handler: &ExceptHandler, checker: &mut Check
except_handler,
checker.locator,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::RaiseWithoutFromInsideExcept) {

View File

@@ -21,7 +21,7 @@ use crate::rules::{
use crate::settings::types::PythonVersion;
/// Run lint rules over an [`Expr`] syntax node.
pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pub(crate) fn expression(expr: &Expr, checker: &Checker) {
match expr {
Expr::Subscript(subscript @ ast::ExprSubscript { value, slice, .. }) => {
// Ex) Optional[...], Union[...]
@@ -201,7 +201,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
check_two_starred_expressions,
expr.range(),
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -515,7 +515,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
match pyflakes::format::FormatSummary::try_from(string_value.to_str()) {
Err(e) => {
if checker.enabled(Rule::StringDotFormatInvalidFormat) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::StringDotFormatInvalidFormat {
message: pyflakes::format::error_to_string(&e),
},
@@ -842,13 +842,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_comprehensions::rules::unnecessary_subscript_reversal(checker, call);
}
if checker.enabled(Rule::UnnecessaryMap) {
flake8_comprehensions::rules::unnecessary_map(
checker,
expr,
checker.semantic.current_expression_parent(),
func,
args,
);
flake8_comprehensions::rules::unnecessary_map(checker, call);
}
if checker.enabled(Rule::UnnecessaryComprehensionInCall) {
flake8_comprehensions::rules::unnecessary_comprehension_in_call(
@@ -912,7 +906,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
pylint::rules::bad_open_mode(checker, call);
}
if checker.enabled(Rule::BadStrStripCall) {
pylint::rules::bad_str_strip_call(checker, func, args);
pylint::rules::bad_str_strip_call(checker, call);
}
if checker.enabled(Rule::ShallowCopyEnviron) {
pylint::rules::shallow_copy_environ(checker, call);
@@ -931,7 +925,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
if checker.enabled(Rule::PytestPatchWithLambda) {
if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(call) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.any_enabled(&[
@@ -1287,7 +1281,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
..
}) => {
if checker.enabled(Rule::PercentFormatUnsupportedFormatCharacter) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::PercentFormatUnsupportedFormatCharacter {
char: c,
},
@@ -1297,7 +1291,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
Err(e) => {
if checker.enabled(Rule::PercentFormatInvalidFormat) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::PercentFormatInvalidFormat {
message: e.to_string(),
},
@@ -1371,7 +1365,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.locator,
checker.settings,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CollectionLiteralConcatenation) {

View File

@@ -5,7 +5,7 @@ use crate::codes::Rule;
use crate::rules::{flake8_bugbear, ruff};
/// Run lint rules over a module.
pub(crate) fn module(suite: &Suite, checker: &mut Checker) {
pub(crate) fn module(suite: &Suite, checker: &Checker) {
if checker.enabled(Rule::FStringDocstring) {
flake8_bugbear::rules::f_string_docstring(checker, suite);
}

View File

@@ -6,7 +6,7 @@ use crate::codes::Rule;
use crate::rules::{flake8_builtins, pycodestyle};
/// Run lint rules over a [`Parameter`] syntax node.
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
pub(crate) fn parameter(parameter: &Parameter, checker: &Checker) {
if checker.enabled(Rule::AmbiguousVariableName) {
pycodestyle::rules::ambiguous_variable_name(
checker,

View File

@@ -5,7 +5,7 @@ use crate::codes::Rule;
use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
/// Run lint rules over a [`Parameters`] syntax node.
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
pub(crate) fn parameters(parameters: &Parameters, checker: &Checker) {
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
}

View File

@@ -39,7 +39,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if !checker.semantic.scope_id.is_global() {
for name in names {
if checker.semantic.nonlocal(name).is_none() {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pylint::rules::NonlocalWithoutBinding {
name: name.to_string(),
},
@@ -59,7 +59,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
&mut checker.semantic.current_statements().skip(1),
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -69,7 +69,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
&mut checker.semantic.current_statements().skip(1),
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -99,7 +99,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::AmbiguousFunctionName) {
if let Some(diagnostic) = pycodestyle::rules::ambiguous_function_name(name) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::InvalidBoolReturnType) {
@@ -128,7 +128,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
&checker.semantic,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.source_type.is_stub() {
@@ -187,7 +187,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
name,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::GlobalStatement) {
@@ -239,7 +239,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
body,
checker.settings.mccabe.max_complexity,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::HardcodedPasswordDefault) {
@@ -265,7 +265,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
body,
checker.settings.pylint.max_returns,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::TooManyBranches) {
@@ -274,7 +274,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
body,
checker.settings.pylint.max_branches,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::TooManyStatements) {
@@ -283,7 +283,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
body,
checker.settings.pylint.max_statements,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.any_enabled(&[
@@ -351,9 +351,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
#[cfg(any(feature = "test-rules", test))]
if checker.enabled(Rule::UnreachableCode) {
checker
.diagnostics
.extend(pylint::rules::in_function(name, body));
checker.report_diagnostics(pylint::rules::in_function(name, body));
}
if checker.enabled(Rule::ReimplementedOperator) {
refurb::rules::reimplemented_operator(checker, &function_def.into());
@@ -456,7 +454,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::AmbiguousClassName) {
if let Some(diagnostic) = pycodestyle::rules::ambiguous_class_name(name) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::InvalidClassName) {
@@ -465,7 +463,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
name,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::ErrorSuffixOnExceptionName) {
@@ -475,7 +473,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
name,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if !checker.source_type.is_stub() {
@@ -615,7 +613,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
flake8_debugger::rules::debugger_import(stmt, None, &alias.name)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::BannedApi) {
@@ -642,7 +640,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
pylint::rules::import_self(alias, checker.module.qualified_name())
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if let Some(asname) = &alias.asname {
@@ -657,7 +655,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::LowercaseImportedAsNonLowercase) {
@@ -670,7 +668,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsLowercase) {
@@ -683,7 +681,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsConstant) {
@@ -694,14 +692,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsAcronym) {
if let Some(diagnostic) = pep8_naming::rules::camelcase_imported_as_acronym(
name, asname, alias, stmt, checker,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -715,7 +713,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.flake8_import_conventions.banned_aliases,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -725,7 +723,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&alias.name,
alias.asname.as_deref(),
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::BuiltinImportShadowing) {
@@ -841,7 +839,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
flake8_pytest_style::rules::import_from(stmt, module, level)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.source_type.is_stub() {
@@ -856,7 +854,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::LateFutureImport) {
if checker.semantic.seen_futures_boundary() {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::LateFutureImport,
stmt.range(),
));
@@ -865,7 +863,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) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
name: helpers::format_import_from(level, module).to_string(),
},
@@ -874,7 +872,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
if checker.enabled(Rule::UndefinedLocalWithImportStar) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithImportStar {
name: helpers::format_import_from(level, module).to_string(),
},
@@ -891,14 +889,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.module.qualified_name(),
checker.settings.flake8_tidy_imports.ban_relative_imports,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::Debugger) {
if let Some(diagnostic) =
flake8_debugger::rules::debugger_import(stmt, module, &alias.name)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::BannedImportAlias) {
@@ -913,7 +911,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.flake8_import_conventions.banned_aliases,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}
@@ -928,7 +926,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::LowercaseImportedAsNonLowercase) {
@@ -941,7 +939,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsLowercase) {
@@ -954,7 +952,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&checker.settings.pep8_naming.ignore_names,
)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsConstant) {
@@ -965,7 +963,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
&checker.settings.pep8_naming.ignore_names,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::CamelcaseImportedAsAcronym) {
@@ -976,7 +974,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
stmt,
checker,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if !checker.source_type.is_stub() {
@@ -996,7 +994,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
names,
checker.module.qualified_name(),
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::BannedImportFrom) {
@@ -1005,7 +1003,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
&helpers::format_import_from(level, module),
&checker.settings.flake8_import_conventions.banned_from,
) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::ByteStringUsage) {
@@ -1174,7 +1172,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.any_enabled(&[Rule::BadVersionInfoComparison, Rule::BadVersionInfoOrder]) {
fn bad_version_info_comparison(
checker: &mut Checker,
checker: &Checker,
test: &Expr,
has_else_clause: bool,
) {
@@ -1221,9 +1219,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
) => {
if !checker.semantic.in_type_checking_block() {
if checker.enabled(Rule::Assert) {
checker
.diagnostics
.push(flake8_bandit::rules::assert_used(stmt));
checker.report_diagnostic(flake8_bandit::rules::assert_used(stmt));
}
}
if checker.enabled(Rule::AssertTuple) {
@@ -1440,7 +1436,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
pyflakes::rules::default_except_not_last(handlers, checker.locator)
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.any_enabled(&[
@@ -1537,7 +1533,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::PandasDfVariableName) {
if let Some(diagnostic) = pandas_vet::rules::assignment_to_df(targets) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker
@@ -1735,7 +1731,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
ruff::rules::asyncio_dangling_task(value, checker.semantic())
{
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
if checker.enabled(Rule::RepeatedAppend) {

View File

@@ -5,7 +5,7 @@ use crate::codes::Rule;
use crate::rules::{flake8_bandit, flake8_pyi, flake8_quotes, pycodestyle, ruff};
/// Run lint rules over a [`StringLike`] syntax nodes.
pub(crate) fn string_like(string_like: StringLike, checker: &mut Checker) {
pub(crate) fn string_like(string_like: StringLike, checker: &Checker) {
if checker.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,

View File

@@ -6,7 +6,7 @@ use crate::rules::flake8_pie;
use crate::rules::refurb;
/// Run lint rules over a suite of [`Stmt`] syntax nodes.
pub(crate) fn suite(suite: &[Stmt], checker: &mut Checker) {
pub(crate) fn suite(suite: &[Stmt], checker: &Checker) {
if checker.enabled(Rule::UnnecessaryPlaceholder) {
flake8_pie::rules::unnecessary_placeholder(checker, suite);
}

View File

@@ -7,7 +7,7 @@ use crate::codes::Rule;
use crate::rules::pyflakes;
/// Run lint rules over all [`UnresolvedReference`] entities in the [`SemanticModel`].
pub(crate) fn unresolved_references(checker: &mut Checker) {
pub(crate) fn unresolved_references(checker: &Checker) {
if !checker.any_enabled(&[Rule::UndefinedLocalWithImportStarUsage, Rule::UndefinedName]) {
return;
}
@@ -15,7 +15,7 @@ pub(crate) fn unresolved_references(checker: &mut Checker) {
for reference in checker.semantic.unresolved_references() {
if reference.is_wildcard_import() {
if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: reference.name(checker.source()).to_string(),
},
@@ -42,7 +42,7 @@ pub(crate) fn unresolved_references(checker: &mut Checker) {
let symbol_name = reference.name(checker.source());
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
pyflakes::rules::UndefinedName {
name: symbol_name.to_string(),
minor_version_builtin_added: version_builtin_was_added(symbol_name),

View File

@@ -9,11 +9,6 @@
//! parent scopes have been fully traversed. Individual rules may also perform internal traversals
//! of the AST.
//!
//! While the [`Checker`] is typically passed by mutable reference to the individual lint rule
//! implementations, most of its constituent components are intended to be treated immutably, with
//! the exception of the [`Diagnostic`] vector, which is intended to be mutated by the individual
//! lint rules. In the future, this should be formalized in the API.
//!
//! The individual [`Visitor`] implementations within the [`Checker`] typically proceed in four
//! steps:
//!
@@ -31,7 +26,7 @@ use std::path::Path;
use itertools::Itertools;
use log::debug;
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_notebook::{CellOffsets, NotebookIndex};
@@ -221,9 +216,9 @@ pub(crate) struct Checker<'a> {
/// A set of deferred nodes to be analyzed after the AST traversal (e.g., `for` loops).
analyze: deferred::Analyze,
/// The cumulative set of diagnostics computed across all lint rules.
pub(crate) diagnostics: Vec<Diagnostic>,
diagnostics: RefCell<Vec<Diagnostic>>,
/// The list of names already seen by flake8-bugbear diagnostics, to avoid duplicate violations.
pub(crate) flake8_bugbear_seen: Vec<TextRange>,
flake8_bugbear_seen: RefCell<FxHashSet<TextRange>>,
/// The end offset of the last visited statement.
last_stmt_end: TextSize,
/// A state describing if a docstring is expected or not.
@@ -271,8 +266,8 @@ impl<'a> Checker<'a> {
semantic,
visit: deferred::Visit::default(),
analyze: deferred::Analyze::default(),
diagnostics: Vec::default(),
flake8_bugbear_seen: Vec::default(),
diagnostics: RefCell::default(),
flake8_bugbear_seen: RefCell::default(),
cell_offsets,
notebook_index,
last_stmt_end: TextSize::default(),
@@ -362,6 +357,30 @@ impl<'a> Checker<'a> {
self.indexer.comment_ranges()
}
/// Push a new [`Diagnostic`] to the collection in the [`Checker`]
pub(crate) fn report_diagnostic(&self, diagnostic: Diagnostic) {
let mut diagnostics = self.diagnostics.borrow_mut();
diagnostics.push(diagnostic);
}
/// Extend the collection of [`Diagnostic`] objects in the [`Checker`]
pub(crate) fn report_diagnostics<I>(&self, diagnostics: I)
where
I: IntoIterator<Item = Diagnostic>,
{
let mut checker_diagnostics = self.diagnostics.borrow_mut();
checker_diagnostics.extend(diagnostics);
}
/// Adds a [`TextRange`] to the set of ranges of variable names
/// flagged in `flake8-bugbear` violations so far.
///
/// Returns whether the value was newly inserted.
pub(crate) fn insert_flake8_bugbear_range(&self, range: TextRange) -> bool {
let mut ranges = self.flake8_bugbear_seen.borrow_mut();
ranges.insert(range)
}
/// Returns the [`Tokens`] for the parsed type annotation if the checker is in a typing context
/// or the parsed source code.
pub(crate) fn tokens(&self) -> &'a Tokens {
@@ -476,9 +495,9 @@ impl<'a> Checker<'a> {
}
/// Push `diagnostic` if the checker is not in a `@no_type_check` context.
pub(crate) fn push_type_diagnostic(&mut self, diagnostic: Diagnostic) {
pub(crate) fn report_type_diagnostic(&self, diagnostic: Diagnostic) {
if !self.semantic.in_no_type_check() {
self.diagnostics.push(diagnostic);
self.report_diagnostic(diagnostic);
}
}
}
@@ -2419,7 +2438,7 @@ impl<'a> Checker<'a> {
self.semantic.restore(snapshot);
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
self.push_type_diagnostic(Diagnostic::new(
self.report_type_diagnostic(Diagnostic::new(
pyflakes::rules::ForwardAnnotationSyntaxError {
parse_error: parse_error.error.to_string(),
},
@@ -2561,7 +2580,7 @@ impl<'a> Checker<'a> {
} else {
if self.semantic.global_scope().uses_star_imports() {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {
self.diagnostics.push(
self.diagnostics.get_mut().push(
Diagnostic::new(
pyflakes::rules::UndefinedLocalWithImportStarUsage {
name: name.to_string(),
@@ -2576,7 +2595,7 @@ impl<'a> Checker<'a> {
if self.settings.preview.is_enabled()
|| !self.path.ends_with("__init__.py")
{
self.diagnostics.push(
self.diagnostics.get_mut().push(
Diagnostic::new(
pyflakes::rules::UndefinedExport {
name: name.to_string(),
@@ -2700,13 +2719,13 @@ pub(crate) fn check_ast(
analyze::deferred_lambdas(&mut checker);
analyze::deferred_for_loops(&mut checker);
analyze::definitions(&mut checker);
analyze::bindings(&mut checker);
analyze::unresolved_references(&mut checker);
analyze::bindings(&checker);
analyze::unresolved_references(&checker);
// Reset the scope to module-level, and check all consumed scopes.
checker.semantic.scope_id = ScopeId::global();
checker.analyze.scopes.push(ScopeId::global());
analyze::deferred_scopes(&mut checker);
analyze::deferred_scopes(&checker);
checker.diagnostics
checker.diagnostics.take()
}

View File

@@ -46,12 +46,7 @@ pub(crate) fn check_file_path(
// flake8-builtins
if settings.rules.enabled(Rule::StdlibModuleShadowing) {
if let Some(diagnostic) = stdlib_module_shadowing(
path,
package,
&settings.flake8_builtins.builtins_allowed_modules,
settings.target_version,
) {
if let Some(diagnostic) = stdlib_module_shadowing(path, settings) {
diagnostics.push(diagnostic);
}
}

View File

@@ -387,25 +387,36 @@ pub(crate) enum ShadowedKind {
}
impl ShadowedKind {
/// Determines the kind of shadowing or conflict for a given variable name.
/// Determines the kind of shadowing or conflict for the proposed new name of a given [`Binding`].
///
/// This function is useful for checking whether or not the `target` of a [`Rename::rename`]
/// This function is useful for checking whether or not the `target` of a [`Renamer::rename`]
/// will shadow another binding.
pub(crate) fn new(name: &str, checker: &Checker, scope_id: ScopeId) -> ShadowedKind {
pub(crate) fn new(binding: &Binding, new_name: &str, checker: &Checker) -> ShadowedKind {
// Check the kind in order of precedence
if is_keyword(name) {
if is_keyword(new_name) {
return ShadowedKind::Keyword;
}
if is_python_builtin(
name,
new_name,
checker.settings.target_version.minor(),
checker.source_type.is_ipynb(),
) {
return ShadowedKind::BuiltIn;
}
if !checker.semantic().is_available_in_scope(name, scope_id) {
let semantic = checker.semantic();
if !semantic.is_available_in_scope(new_name, binding.scope) {
return ShadowedKind::Some;
}
if binding
.references()
.map(|reference_id| semantic.reference(reference_id).scope_id())
.dedup()
.any(|scope| !semantic.is_available_in_scope(new_name, scope))
{
return ShadowedKind::Some;
}

View File

@@ -50,7 +50,7 @@ impl Violation for AirflowDagNoScheduleArgument {
}
/// AIR301
pub(crate) fn dag_no_schedule_argument(checker: &mut Checker, expr: &Expr) {
pub(crate) fn dag_no_schedule_argument(checker: &Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -86,5 +86,5 @@ pub(crate) fn dag_no_schedule_argument(checker: &mut Checker, expr: &Expr) {
// Produce a diagnostic when the `schedule` keyword argument is not found.
let diagnostic = Diagnostic::new(AirflowDagNoScheduleArgument, expr.range());
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}

View File

@@ -83,7 +83,7 @@ impl Violation for Airflow3MovedToProvider {
}
/// AIR303
pub(crate) fn moved_to_provider_in_3(checker: &mut Checker, expr: &Expr) {
pub(crate) fn moved_to_provider_in_3(checker: &Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -112,7 +112,7 @@ enum Replacement {
},
}
fn check_names_moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: TextRange) {
fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRange) {
let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) else {
return;
};
@@ -1018,7 +1018,7 @@ fn check_names_moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: Tex
},
_ => return,
};
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3MovedToProvider {
deprecated: qualified_name.to_string(),
replacement,

View File

@@ -80,7 +80,7 @@ enum Replacement {
}
/// AIR302
pub(crate) fn airflow_3_removal_expr(checker: &mut Checker, expr: &Expr) {
pub(crate) fn airflow_3_removal_expr(checker: &Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -117,10 +117,7 @@ pub(crate) fn airflow_3_removal_expr(checker: &mut Checker, expr: &Expr) {
}
/// AIR302
pub(crate) fn airflow_3_removal_function_def(
checker: &mut Checker,
function_def: &StmtFunctionDef,
) {
pub(crate) fn airflow_3_removal_function_def(checker: &Checker, function_def: &StmtFunctionDef) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -156,7 +153,7 @@ const REMOVED_CONTEXT_KEYS: [&str; 12] = [
/// # 'execution_date' is removed in Airflow 3.0
/// pass
/// ```
fn check_function_parameters(checker: &mut Checker, function_def: &StmtFunctionDef) {
fn check_function_parameters(checker: &Checker, function_def: &StmtFunctionDef) {
if !is_airflow_task(function_def, checker.semantic())
&& !is_execute_method_inherits_from_airflow_operator(function_def, checker.semantic())
{
@@ -166,7 +163,7 @@ fn check_function_parameters(checker: &mut Checker, function_def: &StmtFunctionD
for param in function_def.parameters.iter_non_variadic_params() {
let param_name = param.name();
if REMOVED_CONTEXT_KEYS.contains(&param_name.as_str()) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: param_name.to_string(),
replacement: Replacement::None,
@@ -186,29 +183,25 @@ fn check_function_parameters(checker: &mut Checker, function_def: &StmtFunctionD
///
/// DAG(schedule_interval="@daily")
/// ```
fn check_call_arguments(
checker: &mut Checker,
qualified_name: &QualifiedName,
arguments: &Arguments,
) {
fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, arguments: &Arguments) {
match qualified_name.segments() {
["airflow", .., "DAG" | "dag"] => {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"schedule_interval",
Some("schedule"),
));
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"timetable",
Some("schedule"),
));
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"sla_miss_callback",
None,
));
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"fail_stop",
Some("fail_fast"),
@@ -217,7 +210,7 @@ fn check_call_arguments(
_ => {
if is_airflow_auth_manager(qualified_name.segments()) {
if !arguments.is_empty() {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: String::from("appbuilder"),
replacement: Replacement::Message(
@@ -228,44 +221,42 @@ fn check_call_arguments(
));
}
} else if is_airflow_task_handler(qualified_name.segments()) {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"filename_template",
None,
));
} else if is_airflow_operator(qualified_name.segments()) {
checker
.diagnostics
.extend(diagnostic_for_argument(arguments, "sla", None));
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(arguments, "sla", None));
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"task_concurrency",
Some("max_active_tis_per_dag"),
));
match qualified_name.segments() {
["airflow", .., "operators", "trigger_dagrun", "TriggerDagRunOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"execution_date",
Some("logical_date"),
));
}
["airflow", .., "operators", "datetime", "BranchDateTimeOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
));
}
["airflow", .., "operators", "weekday", "DayOfWeekSensor"] => {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
));
}
["airflow", .., "operators", "weekday", "BranchDayOfWeekOperator"] => {
checker.diagnostics.extend(diagnostic_for_argument(
checker.report_diagnostics(diagnostic_for_argument(
arguments,
"use_task_execution_day",
Some("use_task_logical_date"),
@@ -288,7 +279,7 @@ fn check_call_arguments(
/// info = DatasetLineageInfo()
/// info.dataset
/// ```
fn check_class_attribute(checker: &mut Checker, attribute_expr: &ExprAttribute) {
fn check_class_attribute(checker: &Checker, attribute_expr: &ExprAttribute) {
let ExprAttribute { value, attr, .. } = attribute_expr;
let Some(qualname) = typing::resolve_assignment(value, checker.semantic()) else {
@@ -312,7 +303,7 @@ fn check_class_attribute(checker: &mut Checker, attribute_expr: &ExprAttribute)
};
if let Some(replacement) = replacement {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: attr.to_string(),
replacement,
@@ -350,7 +341,7 @@ fn check_class_attribute(checker: &mut Checker, attribute_expr: &ExprAttribute)
/// def my_task(**context):
/// context.get("conf") # 'conf' is removed in Airflow 3.0
/// ```
fn check_context_key_usage_in_call(checker: &mut Checker, call_expr: &ExprCall) {
fn check_context_key_usage_in_call(checker: &Checker, call_expr: &ExprCall) {
if !in_airflow_task_function(checker.semantic()) {
return;
}
@@ -386,7 +377,7 @@ fn check_context_key_usage_in_call(checker: &mut Checker, call_expr: &ExprCall)
continue;
};
if value == removed_key {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: removed_key.to_string(),
replacement: Replacement::None,
@@ -399,7 +390,7 @@ fn check_context_key_usage_in_call(checker: &mut Checker, call_expr: &ExprCall)
/// Check if a subscript expression accesses a removed Airflow context variable.
/// If a removed key is found, push a corresponding diagnostic.
fn check_context_key_usage_in_subscript(checker: &mut Checker, subscript: &ExprSubscript) {
fn check_context_key_usage_in_subscript(checker: &Checker, subscript: &ExprSubscript) {
if !in_airflow_task_function(checker.semantic()) {
return;
}
@@ -427,7 +418,7 @@ fn check_context_key_usage_in_subscript(checker: &mut Checker, subscript: &ExprS
}
if REMOVED_CONTEXT_KEYS.contains(&key.to_str()) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: key.to_string(),
replacement: Replacement::None,
@@ -463,7 +454,7 @@ fn is_kwarg_parameter(semantic: &SemanticModel, name: &ExprName) -> bool {
/// manager = DatasetManager()
/// manager.register_datsaet_change()
/// ```
fn check_method(checker: &mut Checker, call_expr: &ExprCall) {
fn check_method(checker: &Checker, call_expr: &ExprCall) {
let Expr::Attribute(ExprAttribute { attr, value, .. }) = &*call_expr.func else {
return;
};
@@ -528,7 +519,7 @@ fn check_method(checker: &mut Checker, call_expr: &ExprCall) {
}
};
if let Some(replacement) = replacement {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: attr.to_string(),
replacement,
@@ -552,7 +543,7 @@ fn check_method(checker: &mut Checker, call_expr: &ExprCall) {
/// # Or, directly
/// SubDagOperator()
/// ```
fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) else {
return;
};
@@ -690,16 +681,7 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
["airflow", "operators", "branch_operator", "BaseBranchOperator"] => {
Replacement::Name("airflow.operators.branch.BaseBranchOperator")
}
["airflow", "operators", " dummy", "EmptyOperator"] => {
Replacement::Name("airflow.operators.empty.EmptyOperator")
}
["airflow", "operators", "dummy", "DummyOperator"] => {
Replacement::Name("airflow.operators.empty.EmptyOperator")
}
["airflow", "operators", "dummy_operator", "EmptyOperator"] => {
Replacement::Name("airflow.operators.empty.EmptyOperator")
}
["airflow", "operators", "dummy_operator", "DummyOperator"] => {
["airflow", "operators", "dummy" | "dummy_operator", "EmptyOperator" | "DummyOperator"] => {
Replacement::Name("airflow.operators.empty.EmptyOperator")
}
["airflow", "operators", "email_operator", "EmailOperator"] => {
@@ -728,24 +710,21 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
}
// airflow.sensors
["airflow", "sensors", "external_task", "ExternalTaskSensorLink"] => {
Replacement::Name("airflow.sensors.external_task.ExternalDagLink")
}
["airflow", "sensors", "base_sensor_operator", "BaseSensorOperator"] => {
Replacement::Name("airflow.sensors.base.BaseSensorOperator")
}
["airflow", "sensors", "date_time_sensor", "DateTimeSensor"] => {
Replacement::Name("airflow.sensors.date_time.DateTimeSensor")
}
["airflow", "sensors", "external_task_sensor", "ExternalTaskMarker"] => {
["airflow", "sensors", "external_task" | "external_task_sensor", "ExternalTaskMarker"] => {
Replacement::Name("airflow.sensors.external_task.ExternalTaskMarker")
}
["airflow", "sensors", "external_task_sensor", "ExternalTaskSensor"] => {
Replacement::Name("airflow.sensors.external_task.ExternalTaskSensor")
}
["airflow", "sensors", "external_task_sensor", "ExternalTaskSensorLink"] => {
["airflow", "sensors", "external_task" | "external_task_sensor", "ExternalTaskSensorLink"] => {
Replacement::Name("airflow.sensors.external_task.ExternalDagLink")
}
["airflow", "sensors", "external_task" | "external_task_sensor", "ExternalTaskSensor"] => {
Replacement::Name("airflow.sensors.external_task.ExternalTaskSensor")
}
["airflow", "sensors", "time_delta_sensor", "TimeDeltaSensor"] => {
Replacement::Name("airflow.sensors.time_delta.TimeDeltaSensor")
}
@@ -764,10 +743,9 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
["airflow", "utils", "dates", "days_ago"] => {
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)")
}
["airflow", "utils", "dates", "parse_execution_date"] => Replacement::None,
["airflow", "utils", "dates", "round_time"] => Replacement::None,
["airflow", "utils", "dates", "scale_time_units"] => Replacement::None,
["airflow", "utils", "dates", "infer_time_unit"] => Replacement::None,
["airflow", "utils", "dates", "parse_execution_date" | "round_time" | "scale_time_units" | "infer_time_unit"] => {
Replacement::None
}
// airflow.utils.file
["airflow", "utils", "file", "TemporaryDirectory"] => Replacement::None,
@@ -784,12 +762,10 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
}
// airflow.utils.state
["airflow", "utils", "state", "SHUTDOWN"] => Replacement::None,
["airflow", "utils", "state", "terminating_states"] => Replacement::None,
["airflow", "utils", "state", "SHUTDOWN" | "terminating_states"] => Replacement::None,
// airflow.utils.trigger_rule
["airflow", "utils", "trigger_rule", "TriggerRule", "DUMMY"] => Replacement::None,
["airflow", "utils", "trigger_rule", "TriggerRule", "NONE_FAILED_OR_SKIPPED"] => {
["airflow", "utils", "trigger_rule", "TriggerRule", "DUMMY" | "NONE_FAILED_OR_SKIPPED"] => {
Replacement::None
}
@@ -891,7 +867,7 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
_ => return,
};
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: qualified_name.to_string(),
replacement,
@@ -912,7 +888,7 @@ fn check_name(checker: &mut Checker, expr: &Expr, range: TextRange) {
/// executors = "some.third.party.executor"
/// ```
fn check_airflow_plugin_extension(
checker: &mut Checker,
checker: &Checker,
expr: &Expr,
name: &str,
class_def: &StmtClassDef,
@@ -929,7 +905,7 @@ fn check_airflow_plugin_extension(
)
})
}) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Airflow3Removal {
deprecated: name.to_string(),
replacement: Replacement::Message(

View File

@@ -45,7 +45,7 @@ impl Violation for AirflowVariableNameTaskIdMismatch {
}
/// AIR001
pub(crate) fn variable_name_task_id(checker: &mut Checker, targets: &[Expr], value: &Expr) {
pub(crate) fn variable_name_task_id(checker: &Checker, targets: &[Expr], value: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
@@ -116,5 +116,5 @@ pub(crate) fn variable_name_task_id(checker: &mut Checker, targets: &[Expr], val
},
target.range(),
);
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}

View File

@@ -88,7 +88,7 @@ impl Violation for FastApiNonAnnotatedDependency {
/// FAST002
pub(crate) fn fastapi_non_annotated_dependency(
checker: &mut Checker,
checker: &Checker,
function_def: &ast::StmtFunctionDef,
) {
if !checker.semantic().seen_module(Modules::FASTAPI)
@@ -219,7 +219,7 @@ impl<'a> DependencyCall<'a> {
/// necessary to determine this while generating the fix, thus the need to return an updated
/// `seen_default` here.
fn create_diagnostic(
checker: &mut Checker,
checker: &Checker,
parameter: &DependencyParameter,
dependency_call: Option<DependencyCall>,
mut seen_default: bool,
@@ -304,7 +304,7 @@ fn create_diagnostic(
}
diagnostic.try_set_optional_fix(|| fix);
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
seen_default
}

View File

@@ -74,10 +74,7 @@ impl AlwaysFixableViolation for FastApiRedundantResponseModel {
}
/// FAST001
pub(crate) fn fastapi_redundant_response_model(
checker: &mut Checker,
function_def: &StmtFunctionDef,
) {
pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def: &StmtFunctionDef) {
if !checker.semantic().seen_module(Modules::FASTAPI) {
return;
}
@@ -98,7 +95,7 @@ pub(crate) fn fastapi_redundant_response_model(
)
.map(Fix::unsafe_edit)
});
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}

View File

@@ -105,7 +105,7 @@ impl Violation for FastApiUnusedPathParameter {
/// FAST003
pub(crate) fn fastapi_unused_path_parameter(
checker: &mut Checker,
checker: &Checker,
function_def: &ast::StmtFunctionDef,
) {
if !checker.semantic().seen_module(Modules::FASTAPI) {
@@ -163,7 +163,6 @@ pub(crate) fn fastapi_unused_path_parameter(
}
// Check if any of the path parameters are not in the function signature.
let mut diagnostics = vec![];
for (path_param, range) in path_params {
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
if !is_identifier(path_param) {
@@ -203,10 +202,8 @@ pub(crate) fn fastapi_unused_path_parameter(
checker.locator().contents(),
)));
}
diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
checker.diagnostics.extend(diagnostics);
}
/// Returns an iterator over the non-positional-only, non-variadic parameters of a function.

View File

@@ -223,7 +223,7 @@ impl Violation for SysVersionCmpStr10 {
}
/// YTT103, YTT201, YTT203, YTT204, YTT302
pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], comparators: &[Expr]) {
pub(crate) fn compare(checker: &Checker, left: &Expr, ops: &[CmpOp], comparators: &[Expr]) {
match left {
Expr::Subscript(ast::ExprSubscript { value, slice, .. })
if is_sys(value, "version_info", checker.semantic()) =>
@@ -243,9 +243,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
) = (ops, comparators)
{
if *n == 3 && checker.enabled(Rule::SysVersionInfo0Eq3) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfo0Eq3, left.range()));
checker.report_diagnostic(Diagnostic::new(
SysVersionInfo0Eq3,
left.range(),
));
}
}
} else if *i == 1 {
@@ -258,9 +259,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
) = (ops, comparators)
{
if checker.enabled(Rule::SysVersionInfo1CmpInt) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfo1CmpInt, left.range()));
checker.report_diagnostic(Diagnostic::new(
SysVersionInfo1CmpInt,
left.range(),
));
}
}
}
@@ -279,9 +281,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
) = (ops, comparators)
{
if checker.enabled(Rule::SysVersionInfoMinorCmpInt) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfoMinorCmpInt, left.range()));
checker.report_diagnostic(Diagnostic::new(
SysVersionInfoMinorCmpInt,
left.range(),
));
}
}
}
@@ -297,14 +300,10 @@ pub(crate) fn compare(checker: &mut Checker, left: &Expr, ops: &[CmpOp], compara
{
if value.len() == 1 {
if checker.enabled(Rule::SysVersionCmpStr10) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionCmpStr10, left.range()));
checker.report_diagnostic(Diagnostic::new(SysVersionCmpStr10, left.range()));
}
} else if checker.enabled(Rule::SysVersionCmpStr3) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionCmpStr3, left.range()));
checker.report_diagnostic(Diagnostic::new(SysVersionCmpStr3, left.range()));
}
}
}

View File

@@ -46,7 +46,7 @@ impl Violation for SixPY3 {
}
/// YTT202
pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
pub(crate) fn name_or_attribute(checker: &Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::SIX) {
return;
}
@@ -56,8 +56,6 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
.resolve_qualified_name(expr)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["six", "PY3"]))
{
checker
.diagnostics
.push(Diagnostic::new(SixPY3, expr.range()));
checker.report_diagnostic(Diagnostic::new(SixPY3, expr.range()));
}
}

View File

@@ -168,7 +168,7 @@ impl Violation for SysVersionSlice1 {
}
/// YTT101, YTT102, YTT301, YTT303
pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
pub(crate) fn subscript(checker: &Checker, value: &Expr, slice: &Expr) {
if is_sys(value, "version", checker.semantic()) {
match slice {
Expr::Slice(ast::ExprSlice {
@@ -183,13 +183,9 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
}) = upper.as_ref()
{
if *i == 1 && checker.enabled(Rule::SysVersionSlice1) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice1, value.range()));
checker.report_diagnostic(Diagnostic::new(SysVersionSlice1, value.range()));
} else if *i == 3 && checker.enabled(Rule::SysVersionSlice3) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice3, value.range()));
checker.report_diagnostic(Diagnostic::new(SysVersionSlice3, value.range()));
}
}
}
@@ -199,13 +195,9 @@ pub(crate) fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
..
}) => {
if *i == 2 && checker.enabled(Rule::SysVersion2) {
checker
.diagnostics
.push(Diagnostic::new(SysVersion2, value.range()));
checker.report_diagnostic(Diagnostic::new(SysVersion2, value.range()));
} else if *i == 0 && checker.enabled(Rule::SysVersion0) {
checker
.diagnostics
.push(Diagnostic::new(SysVersion0, value.range()));
checker.report_diagnostic(Diagnostic::new(SysVersion0, value.range()));
}
}

View File

@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
snapshot_kind: text
---
auto_return_type.py:1:5: ANN201 [*] Missing return type annotation for public function `func`
|
@@ -97,7 +96,7 @@ auto_return_type.py:27:5: ANN201 [*] Missing return type annotation for public f
| ^^^^ ANN201
28 | return 1 or 2.5 if x > 0 else 1.5 or "str"
|
= help: Add return type annotation: `Union[str | float]`
= help: Add return type annotation: `Union[str, float]`
Unsafe fix
1 |+from typing import Union
@@ -109,7 +108,7 @@ auto_return_type.py:27:5: ANN201 [*] Missing return type annotation for public f
25 26 |
26 27 |
27 |-def func(x: int):
28 |+def func(x: int) -> Union[str | float]:
28 |+def func(x: int) -> Union[str, float]:
28 29 | return 1 or 2.5 if x > 0 else 1.5 or "str"
29 30 |
30 31 |
@@ -120,7 +119,7 @@ auto_return_type.py:31:5: ANN201 [*] Missing return type annotation for public f
| ^^^^ ANN201
32 | return 1 + 2.5 if x > 0 else 1.5 or "str"
|
= help: Add return type annotation: `Union[str | float]`
= help: Add return type annotation: `Union[str, float]`
Unsafe fix
1 |+from typing import Union
@@ -132,7 +131,7 @@ auto_return_type.py:31:5: ANN201 [*] Missing return type annotation for public f
29 30 |
30 31 |
31 |-def func(x: int):
32 |+def func(x: int) -> Union[str | float]:
32 |+def func(x: int) -> Union[str, float]:
32 33 | return 1 + 2.5 if x > 0 else 1.5 or "str"
33 34 |
34 35 |
@@ -204,7 +203,7 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f
60 | if not x:
61 | return 1
|
= help: Add return type annotation: `Union[str | int | None]`
= help: Add return type annotation: `Union[str, int, None]`
Unsafe fix
1 |+from typing import Union
@@ -216,7 +215,7 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f
57 58 |
58 59 |
59 |-def func(x: int):
60 |+def func(x: int) -> Union[str | int | None]:
60 |+def func(x: int) -> Union[str, int, None]:
60 61 | if not x:
61 62 | return 1
62 63 | elif x > 5:
@@ -294,7 +293,7 @@ auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public f
83 | match x:
84 | case [1, 2, 3]:
|
= help: Add return type annotation: `Union[str | int | None]`
= help: Add return type annotation: `Union[str, int, None]`
Unsafe fix
1 |+from typing import Union
@@ -306,7 +305,7 @@ auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public f
80 81 |
81 82 |
82 |-def func(x: int):
83 |+def func(x: int) -> Union[str | int | None]:
83 |+def func(x: int) -> Union[str, int, None]:
83 84 | match x:
84 85 | case [1, 2, 3]:
85 86 | return 1
@@ -853,7 +852,7 @@ auto_return_type.py:299:5: ANN201 [*] Missing return type annotation for public
300 | match x:
301 | case [1, 2, 3]:
|
= help: Add return type annotation: `Union[str | int]`
= help: Add return type annotation: `Union[str, int]`
Unsafe fix
214 214 | return 1
@@ -869,7 +868,7 @@ auto_return_type.py:299:5: ANN201 [*] Missing return type annotation for public
297 297 |
298 298 |
299 |-def func(x: int):
299 |+def func(x: int) -> Union[str | int]:
299 |+def func(x: int) -> Union[str, int]:
300 300 | match x:
301 301 | case [1, 2, 3]:
302 302 | return 1

View File

@@ -51,7 +51,7 @@ impl Violation for AsyncBusyWait {
}
/// ASYNC110
pub(crate) fn async_busy_wait(checker: &mut Checker, while_stmt: &ast::StmtWhile) {
pub(crate) fn async_busy_wait(checker: &Checker, while_stmt: &ast::StmtWhile) {
// The body should be a single `await` call.
let [stmt] = while_stmt.body.as_slice() else {
return;
@@ -74,7 +74,7 @@ pub(crate) fn async_busy_wait(checker: &mut Checker, while_stmt: &ast::StmtWhile
qualified_name.segments(),
["trio" | "anyio", "sleep" | "sleep_until"] | ["asyncio", "sleep"]
) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
AsyncBusyWait {
module: AsyncModule::try_from(&qualified_name).unwrap(),
},

View File

@@ -87,10 +87,7 @@ impl Violation for AsyncFunctionWithTimeout {
}
/// ASYNC109
pub(crate) fn async_function_with_timeout(
checker: &mut Checker,
function_def: &ast::StmtFunctionDef,
) {
pub(crate) fn async_function_with_timeout(checker: &Checker, function_def: &ast::StmtFunctionDef) {
// Detect `async` calls with a `timeout` argument.
if !function_def.is_async {
return;
@@ -115,7 +112,7 @@ pub(crate) fn async_function_with_timeout(
return;
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
AsyncFunctionWithTimeout { module },
timeout.range(),
));

View File

@@ -51,7 +51,7 @@ impl AlwaysFixableViolation for AsyncZeroSleep {
}
/// ASYNC115
pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn async_zero_sleep(checker: &Checker, call: &ExprCall) {
if !(checker.semantic().seen_module(Modules::TRIO)
|| checker.semantic().seen_module(Modules::ANYIO))
{
@@ -103,6 +103,6 @@ pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) {
let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range());
Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit]))
});
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}

View File

@@ -62,7 +62,7 @@ fn is_blocking_http_call(qualified_name: &QualifiedName) -> bool {
}
/// ASYNC210
pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn blocking_http_call(checker: &Checker, call: &ExprCall) {
if checker.semantic().in_async_context() {
if checker
.semantic()
@@ -70,7 +70,7 @@ pub(crate) fn blocking_http_call(checker: &mut Checker, call: &ExprCall) {
.as_ref()
.is_some_and(is_blocking_http_call)
{
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
BlockingHttpCallInAsyncFunction,
call.func.range(),
));

View File

@@ -44,7 +44,7 @@ impl Violation for BlockingOpenCallInAsyncFunction {
}
/// ASYNC230
pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn blocking_open_call(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().in_async_context() {
return;
}
@@ -52,7 +52,7 @@ pub(crate) fn blocking_open_call(checker: &mut Checker, call: &ast::ExprCall) {
if is_open_call(&call.func, checker.semantic())
|| is_open_call_from_pathlib(call.func.as_ref(), checker.semantic())
{
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
BlockingOpenCallInAsyncFunction,
call.func.range(),
));

View File

@@ -112,7 +112,7 @@ impl Violation for WaitForProcessInAsyncFunction {
}
/// ASYNC220, ASYNC221, ASYNC222
pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn blocking_process_invocation(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().in_async_context() {
return;
}
@@ -146,7 +146,7 @@ pub(crate) fn blocking_process_invocation(checker: &mut Checker, call: &ast::Exp
};
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, call.func.range());
if checker.enabled(diagnostic.kind.rule()) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}

View File

@@ -43,7 +43,7 @@ fn is_blocking_sleep(qualified_name: &QualifiedName) -> bool {
}
/// ASYNC251
pub(crate) fn blocking_sleep(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn blocking_sleep(checker: &Checker, call: &ExprCall) {
if checker.semantic().in_async_context() {
if checker
.semantic()
@@ -51,7 +51,7 @@ pub(crate) fn blocking_sleep(checker: &mut Checker, call: &ExprCall) {
.as_ref()
.is_some_and(is_blocking_sleep)
{
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
BlockingSleepInAsyncFunction,
call.func.range(),
));

View File

@@ -53,7 +53,7 @@ impl Violation for CancelScopeNoCheckpoint {
/// ASYNC100
pub(crate) fn cancel_scope_no_checkpoint(
checker: &mut Checker,
checker: &Checker,
with_stmt: &StmtWith,
with_items: &[WithItem],
) {
@@ -98,7 +98,7 @@ pub(crate) fn cancel_scope_no_checkpoint(
return;
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
CancelScopeNoCheckpoint { method_name },
with_stmt.range,
));

View File

@@ -56,7 +56,7 @@ impl Violation for LongSleepNotForever {
}
/// ASYNC116
pub(crate) fn long_sleep_not_forever(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn long_sleep_not_forever(checker: &Checker, call: &ExprCall) {
if !(checker.semantic().seen_module(Modules::TRIO)
|| checker.semantic().seen_module(Modules::ANYIO))
{
@@ -127,5 +127,5 @@ pub(crate) fn long_sleep_not_forever(checker: &mut Checker, call: &ExprCall) {
let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range());
Ok(Fix::unsafe_edits(import_edit, [reference_edit, arg_edit]))
});
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}

View File

@@ -51,7 +51,7 @@ impl Violation for TrioSyncCall {
}
/// ASYNC105
pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn sync_call(checker: &Checker, call: &ExprCall) {
if !checker.semantic().seen_module(Modules::TRIO) {
return;
}
@@ -91,5 +91,5 @@ pub(crate) fn sync_call(checker: &mut Checker, call: &ExprCall) {
call.func.start(),
)));
}
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}

View File

@@ -61,7 +61,7 @@ enum Reason {
}
/// S103
pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn bad_file_permissions(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::OS) {
return;
}
@@ -78,7 +78,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
// The mask is a valid integer value -- check for overly permissive permissions.
Ok(Some(mask)) => {
if (mask & WRITE_WORLD > 0) || (mask & EXECUTE_GROUP > 0) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
BadFilePermissions {
reason: Reason::Permissive(mask),
},
@@ -88,7 +88,7 @@ pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall)
}
// The mask is an invalid integer value (i.e., it's out of range).
Err(_) => {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
BadFilePermissions {
reason: Reason::Invalid,
},

View File

@@ -44,7 +44,7 @@ impl Violation for DjangoExtra {
}
/// S610
pub(crate) fn django_extra(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn django_extra(checker: &Checker, call: &ast::ExprCall) {
let Expr::Attribute(ExprAttribute { attr, .. }) = call.func.as_ref() else {
return;
};
@@ -54,9 +54,7 @@ pub(crate) fn django_extra(checker: &mut Checker, call: &ast::ExprCall) {
}
if is_call_insecure(call) {
checker
.diagnostics
.push(Diagnostic::new(DjangoExtra, call.arguments.range()));
checker.report_diagnostic(Diagnostic::new(DjangoExtra, call.arguments.range()));
}
}

View File

@@ -35,7 +35,7 @@ impl Violation for DjangoRawSql {
}
/// S611
pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn django_raw_sql(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::DJANGO) {
return;
}
@@ -55,9 +55,7 @@ pub(crate) fn django_raw_sql(checker: &mut Checker, call: &ast::ExprCall) {
.find_argument_value("sql", 0)
.is_some_and(Expr::is_string_literal_expr)
{
checker
.diagnostics
.push(Diagnostic::new(DjangoRawSql, call.func.range()));
checker.report_diagnostic(Diagnostic::new(DjangoRawSql, call.func.range()));
}
}
}

View File

@@ -32,10 +32,8 @@ impl Violation for ExecBuiltin {
}
/// S102
pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
pub(crate) fn exec_used(checker: &Checker, func: &Expr) {
if checker.semantic().match_builtin_expr(func, "exec") {
checker
.diagnostics
.push(Diagnostic::new(ExecBuiltin, func.range()));
checker.report_diagnostic(Diagnostic::new(ExecBuiltin, func.range()));
}
}

View File

@@ -47,7 +47,7 @@ impl Violation for FlaskDebugTrue {
}
/// S201
pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn flask_debug_true(checker: &Checker, call: &ExprCall) {
let Expr::Attribute(ExprAttribute { attr, value, .. }) = call.func.as_ref() else {
return;
};
@@ -67,8 +67,6 @@ pub(crate) fn flask_debug_true(checker: &mut Checker, call: &ExprCall) {
if typing::resolve_assignment(value, checker.semantic())
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["flask", "Flask"]))
{
checker
.diagnostics
.push(Diagnostic::new(FlaskDebugTrue, debug_argument.range()));
checker.report_diagnostic(Diagnostic::new(FlaskDebugTrue, debug_argument.range()));
}
}

View File

@@ -37,13 +37,12 @@ impl Violation for HardcodedBindAllInterfaces {
}
/// S104
pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: StringLike) {
pub(crate) fn hardcoded_bind_all_interfaces(checker: &Checker, string: StringLike) {
match string {
StringLike::String(ast::ExprStringLiteral { value, .. }) => {
if value == "0.0.0.0" {
checker
.diagnostics
.push(Diagnostic::new(HardcodedBindAllInterfaces, string.range()));
.report_diagnostic(Diagnostic::new(HardcodedBindAllInterfaces, string.range()));
}
}
StringLike::FString(ast::ExprFString { value, .. }) => {
@@ -51,15 +50,16 @@ pub(crate) fn hardcoded_bind_all_interfaces(checker: &mut Checker, string: Strin
match part {
ast::FStringPart::Literal(literal) => {
if &**literal == "0.0.0.0" {
checker
.diagnostics
.push(Diagnostic::new(HardcodedBindAllInterfaces, literal.range()));
checker.report_diagnostic(Diagnostic::new(
HardcodedBindAllInterfaces,
literal.range(),
));
}
}
ast::FStringPart::FString(f_string) => {
for literal in f_string.elements.literals() {
if &**literal == "0.0.0.0" {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HardcodedBindAllInterfaces,
literal.range(),
));

View File

@@ -69,13 +69,13 @@ fn check_password_kwarg(parameter: &Parameter, default: &Expr) -> Option<Diagnos
}
/// S107
pub(crate) fn hardcoded_password_default(checker: &mut Checker, parameters: &Parameters) {
pub(crate) fn hardcoded_password_default(checker: &Checker, parameters: &Parameters) {
for parameter in parameters.iter_non_variadic_params() {
let Some(default) = parameter.default() else {
continue;
};
if let Some(diagnostic) = check_password_kwarg(&parameter.parameter, default) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
}

View File

@@ -51,20 +51,18 @@ impl Violation for HardcodedPasswordFuncArg {
}
/// S106
pub(crate) fn hardcoded_password_func_arg(checker: &mut Checker, keywords: &[Keyword]) {
checker
.diagnostics
.extend(keywords.iter().filter_map(|keyword| {
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
let arg = keyword.arg.as_ref()?;
if !matches_password_name(arg) {
return None;
}
Some(Diagnostic::new(
HardcodedPasswordFuncArg {
name: arg.to_string(),
},
keyword.range(),
))
}));
pub(crate) fn hardcoded_password_func_arg(checker: &Checker, keywords: &[Keyword]) {
checker.report_diagnostics(keywords.iter().filter_map(|keyword| {
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
let arg = keyword.arg.as_ref()?;
if !matches_password_name(arg) {
return None;
}
Some(Diagnostic::new(
HardcodedPasswordFuncArg {
name: arg.to_string(),
},
keyword.range(),
))
}));
}

View File

@@ -72,37 +72,31 @@ fn password_target(target: &Expr) -> Option<&str> {
/// S105
pub(crate) fn compare_to_hardcoded_password_string(
checker: &mut Checker,
checker: &Checker,
left: &Expr,
comparators: &[Expr],
) {
checker
.diagnostics
.extend(comparators.iter().filter_map(|comp| {
string_literal(comp).filter(|string| !string.is_empty())?;
let name = password_target(left)?;
Some(Diagnostic::new(
HardcodedPasswordString {
name: name.to_string(),
},
comp.range(),
))
}));
checker.report_diagnostics(comparators.iter().filter_map(|comp| {
string_literal(comp).filter(|string| !string.is_empty())?;
let name = password_target(left)?;
Some(Diagnostic::new(
HardcodedPasswordString {
name: name.to_string(),
},
comp.range(),
))
}));
}
/// S105
pub(crate) fn assign_hardcoded_password_string(
checker: &mut Checker,
value: &Expr,
targets: &[Expr],
) {
pub(crate) fn assign_hardcoded_password_string(checker: &Checker, value: &Expr, targets: &[Expr]) {
if string_literal(value)
.filter(|string| !string.is_empty())
.is_some()
{
for target in targets {
if let Some(name) = password_target(target) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HardcodedPasswordString {
name: name.to_string(),
},

View File

@@ -55,7 +55,7 @@ impl Violation for HardcodedSQLExpression {
}
/// S608
pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) {
let content = match expr {
// "select * from table where val = " + "str" + ...
Expr::BinOp(ast::ExprBinOp {
@@ -105,9 +105,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &mut Checker, expr: &Expr) {
};
if SQL_REGEX.is_match(&content) {
checker
.diagnostics
.push(Diagnostic::new(HardcodedSQLExpression, expr.range()));
checker.report_diagnostic(Diagnostic::new(HardcodedSQLExpression, expr.range()));
}
}

View File

@@ -56,7 +56,7 @@ impl Violation for HardcodedTempFile {
}
/// S108
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike) {
pub(crate) fn hardcoded_tmp_directory(checker: &Checker, string: StringLike) {
match string {
StringLike::String(ast::ExprStringLiteral { value, .. }) => {
check(checker, value.to_str(), string.range());
@@ -79,7 +79,7 @@ pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, string: StringLike)
}
}
fn check(checker: &mut Checker, value: &str, range: TextRange) {
fn check(checker: &Checker, value: &str, range: TextRange) {
if !checker
.settings
.flake8_bandit
@@ -102,7 +102,7 @@ fn check(checker: &mut Checker, value: &str, range: TextRange) {
}
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HardcodedTempFile {
string: value.to_string(),
},

View File

@@ -64,7 +64,7 @@ impl Violation for HashlibInsecureHashFunction {
}
/// S324
pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn hashlib_insecure_hash_functions(checker: &Checker, call: &ast::ExprCall) {
if !checker
.semantic()
.seen_module(Modules::HASHLIB | Modules::CRYPT)
@@ -105,7 +105,7 @@ pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast:
}
fn detect_insecure_hashlib_calls(
checker: &mut Checker,
checker: &Checker,
call: &ast::ExprCall,
hashlib_call: HashlibCall,
) {
@@ -128,7 +128,7 @@ fn detect_insecure_hashlib_calls(
hash_func_name,
"md4" | "md5" | "sha" | "sha1" | "MD4" | "MD5" | "SHA" | "SHA1"
) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HashlibInsecureHashFunction {
library: "hashlib".to_string(),
string: hash_func_name.to_string(),
@@ -138,7 +138,7 @@ fn detect_insecure_hashlib_calls(
}
}
HashlibCall::WeakHash(func_name) => {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HashlibInsecureHashFunction {
library: "hashlib".to_string(),
string: (*func_name).to_string(),
@@ -149,7 +149,7 @@ fn detect_insecure_hashlib_calls(
}
}
fn detect_insecure_crypt_calls(checker: &mut Checker, call: &ast::ExprCall) {
fn detect_insecure_crypt_calls(checker: &Checker, call: &ast::ExprCall) {
let Some(method) = checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -173,7 +173,7 @@ fn detect_insecure_crypt_calls(checker: &mut Checker, call: &ast::ExprCall) {
qualified_name.segments(),
["crypt", "METHOD_CRYPT" | "METHOD_MD5" | "METHOD_BLOWFISH"]
) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
HashlibInsecureHashFunction {
library: "crypt".to_string(),
string: qualified_name.to_string(),

View File

@@ -56,7 +56,7 @@ impl Violation for Jinja2AutoescapeFalse {
}
/// S701
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn jinja2_autoescape_false(checker: &Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -70,20 +70,20 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCal
Expr::Call(ast::ExprCall { func, .. }) => {
if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
if id != "select_autoescape" {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Jinja2AutoescapeFalse { value: true },
keyword.range(),
));
}
}
}
_ => checker.diagnostics.push(Diagnostic::new(
_ => checker.report_diagnostic(Diagnostic::new(
Jinja2AutoescapeFalse { value: true },
keyword.range(),
)),
}
} else {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
Jinja2AutoescapeFalse { value: false },
call.func.range(),
));

View File

@@ -35,7 +35,7 @@ impl Violation for LoggingConfigInsecureListen {
}
/// S612
pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn logging_config_insecure_listen(checker: &Checker, call: &ast::ExprCall) {
if !checker.semantic().seen_module(Modules::LOGGING) {
return;
}
@@ -51,7 +51,7 @@ pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::
return;
}
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
LoggingConfigInsecureListen,
call.func.range(),
));

View File

@@ -42,7 +42,7 @@ impl Violation for MakoTemplates {
}
/// S702
pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn mako_templates(checker: &Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -50,8 +50,6 @@ pub(crate) fn mako_templates(checker: &mut Checker, call: &ast::ExprCall) {
matches!(qualified_name.segments(), ["mako", "template", "Template"])
})
{
checker
.diagnostics
.push(Diagnostic::new(MakoTemplates, call.func.range()));
checker.report_diagnostic(Diagnostic::new(MakoTemplates, call.func.range()));
}
}

View File

@@ -37,7 +37,7 @@ impl Violation for ParamikoCall {
}
/// S601
pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
pub(crate) fn paramiko_call(checker: &Checker, func: &Expr) {
if checker
.semantic()
.resolve_qualified_name(func)
@@ -45,8 +45,6 @@ pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
matches!(qualified_name.segments(), ["paramiko", "exec_command"])
})
{
checker
.diagnostics
.push(Diagnostic::new(ParamikoCall, func.range()));
checker.report_diagnostic(Diagnostic::new(ParamikoCall, func.range()));
}
}

View File

@@ -46,7 +46,7 @@ impl Violation for RequestWithNoCertValidation {
}
/// S501
pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn request_with_no_cert_validation(checker: &Checker, call: &ast::ExprCall) {
if let Some(target) = checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -61,7 +61,7 @@ pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast:
{
if let Some(keyword) = call.arguments.find_keyword("verify") {
if is_const_false(&keyword.value) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
RequestWithNoCertValidation {
string: target.to_string(),
},

View File

@@ -51,7 +51,7 @@ impl Violation for RequestWithoutTimeout {
}
/// S113
pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn request_without_timeout(checker: &Checker, call: &ast::ExprCall) {
if let Some(module) = checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -67,13 +67,13 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCal
{
if let Some(keyword) = call.arguments.find_keyword("timeout") {
if keyword.value.is_none_literal_expr() {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
RequestWithoutTimeout { implicit: false, module: module.to_string() },
keyword.range(),
));
}
} else if module == "requests" {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
RequestWithoutTimeout { implicit: true, module: module.to_string() },
call.func.range(),
));

View File

@@ -288,7 +288,7 @@ impl Violation for UnixCommandWildcardInjection {
}
/// S602, S603, S604, S605, S606, S607, S609
pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn shell_injection(checker: &Checker, call: &ast::ExprCall) {
let call_kind = get_call_kind(&call.func, checker.semantic());
let shell_keyword = find_shell_keyword(&call.arguments, checker.semantic());
@@ -300,7 +300,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
truthiness: truthiness @ (Truthiness::True | Truthiness::Truthy),
}) => {
if checker.enabled(Rule::SubprocessPopenWithShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SubprocessPopenWithShellEqualsTrue {
safety: Safety::from(arg),
is_exact: matches!(truthiness, Truthiness::True),
@@ -315,7 +315,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
Truthiness::False | Truthiness::Falsey | Truthiness::None | Truthiness::Unknown,
}) => {
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SubprocessWithoutShellEqualsTrue,
call.func.range(),
));
@@ -324,7 +324,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
// S603
None => {
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SubprocessWithoutShellEqualsTrue,
call.func.range(),
));
@@ -338,7 +338,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
{
// S604
if checker.enabled(Rule::CallWithShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
CallWithShellEqualsTrue {
is_exact: matches!(truthiness, Truthiness::True),
},
@@ -351,7 +351,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
if checker.enabled(Rule::StartProcessWithAShell) {
if matches!(call_kind, Some(CallKind::Shell)) {
if let Some(arg) = call.arguments.args.first() {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
StartProcessWithAShell {
safety: Safety::from(arg),
},
@@ -364,9 +364,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
// S606
if checker.enabled(Rule::StartProcessWithNoShell) {
if matches!(call_kind, Some(CallKind::NoShell)) {
checker
.diagnostics
.push(Diagnostic::new(StartProcessWithNoShell, call.func.range()));
checker.report_diagnostic(Diagnostic::new(StartProcessWithNoShell, call.func.range()));
}
}
@@ -375,9 +373,10 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
if call_kind.is_some() {
if let Some(arg) = call.arguments.args.first() {
if is_partial_path(arg) {
checker
.diagnostics
.push(Diagnostic::new(StartProcessWithPartialPath, arg.range()));
checker.report_diagnostic(Diagnostic::new(
StartProcessWithPartialPath,
arg.range(),
));
}
}
}
@@ -398,9 +397,10 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
{
if let Some(arg) = call.arguments.args.first() {
if is_wildcard_command(arg) {
checker
.diagnostics
.push(Diagnostic::new(UnixCommandWildcardInjection, arg.range()));
checker.report_diagnostic(Diagnostic::new(
UnixCommandWildcardInjection,
arg.range(),
));
}
}
}

View File

@@ -41,7 +41,7 @@ impl Violation for SnmpInsecureVersion {
}
/// S508
pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn snmp_insecure_version(checker: &Checker, call: &ast::ExprCall) {
if checker
.semantic()
.resolve_qualified_name(&call.func)
@@ -60,9 +60,7 @@ pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall)
..
})
) {
checker
.diagnostics
.push(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
checker.report_diagnostic(Diagnostic::new(SnmpInsecureVersion, keyword.range()));
}
}
}

View File

@@ -40,7 +40,7 @@ impl Violation for SnmpWeakCryptography {
}
/// S509
pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall) {
pub(crate) fn snmp_weak_cryptography(checker: &Checker, call: &ast::ExprCall) {
if call.arguments.len() < 3 {
if checker
.semantic()
@@ -52,9 +52,7 @@ pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall
)
})
{
checker
.diagnostics
.push(Diagnostic::new(SnmpWeakCryptography, call.func.range()));
checker.report_diagnostic(Diagnostic::new(SnmpWeakCryptography, call.func.range()));
}
}
}

View File

@@ -44,7 +44,7 @@ impl Violation for SSHNoHostKeyVerification {
}
/// S507
pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn ssh_no_host_key_verification(checker: &Checker, call: &ExprCall) {
let Expr::Attribute(ExprAttribute { attr, value, .. }) = call.func.as_ref() else {
return;
};
@@ -78,7 +78,7 @@ pub(crate) fn ssh_no_host_key_verification(checker: &mut Checker, call: &ExprCal
["paramiko", "client", "SSHClient"] | ["paramiko", "SSHClient"]
)
}) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SSHNoHostKeyVerification,
policy_argument.range(),
));

View File

@@ -48,7 +48,7 @@ impl Violation for SslInsecureVersion {
}
/// S502
pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn ssl_insecure_version(checker: &Checker, call: &ExprCall) {
let Some(keyword) = checker
.semantic()
.resolve_qualified_name(call.func.as_ref())
@@ -68,7 +68,7 @@ pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
match &keyword.value {
Expr::Name(ast::ExprName { id, .. }) => {
if is_insecure_protocol(id) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SslInsecureVersion {
protocol: id.to_string(),
},
@@ -78,7 +78,7 @@ pub(crate) fn ssl_insecure_version(checker: &mut Checker, call: &ExprCall) {
}
Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
if is_insecure_protocol(attr) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SslInsecureVersion {
protocol: attr.to_string(),
},

View File

@@ -48,7 +48,7 @@ impl Violation for SslWithBadDefaults {
}
/// S503
pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFunctionDef) {
pub(crate) fn ssl_with_bad_defaults(checker: &Checker, function_def: &StmtFunctionDef) {
for default in function_def
.parameters
.iter_non_variadic_params()
@@ -57,7 +57,7 @@ pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFu
match default {
Expr::Name(ast::ExprName { id, range, .. }) => {
if is_insecure_protocol(id.as_str()) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SslWithBadDefaults {
protocol: id.to_string(),
},
@@ -67,7 +67,7 @@ pub(crate) fn ssl_with_bad_defaults(checker: &mut Checker, function_def: &StmtFu
}
Expr::Attribute(ast::ExprAttribute { attr, range, .. }) => {
if is_insecure_protocol(attr.as_str()) {
checker.diagnostics.push(Diagnostic::new(
checker.report_diagnostic(Diagnostic::new(
SslWithBadDefaults {
protocol: attr.to_string(),
},

View File

@@ -36,16 +36,14 @@ impl Violation for SslWithNoVersion {
}
/// S504
pub(crate) fn ssl_with_no_version(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn ssl_with_no_version(checker: &Checker, call: &ExprCall) {
if checker
.semantic()
.resolve_qualified_name(call.func.as_ref())
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["ssl", "wrap_socket"]))
{
if call.arguments.find_keyword("ssl_version").is_none() {
checker
.diagnostics
.push(Diagnostic::new(SslWithNoVersion, call.range()));
checker.report_diagnostic(Diagnostic::new(SslWithNoVersion, call.range()));
}
}
}

View File

@@ -908,7 +908,7 @@ impl Violation for SuspiciousFTPLibUsage {
}
}
pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
pub(crate) fn suspicious_function_call(checker: &Checker, call: &ExprCall) {
suspicious_function(
checker,
call.func.as_ref(),
@@ -917,7 +917,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
);
}
pub(crate) fn suspicious_function_reference(checker: &mut Checker, func: &Expr) {
pub(crate) fn suspicious_function_reference(checker: &Checker, func: &Expr) {
if checker.settings.preview.is_disabled() {
return;
}
@@ -953,7 +953,7 @@ pub(crate) fn suspicious_function_reference(checker: &mut Checker, func: &Expr)
/// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323
fn suspicious_function(
checker: &mut Checker,
checker: &Checker,
func: &Expr,
arguments: Option<&Arguments>,
range: TextRange,
@@ -1176,12 +1176,12 @@ fn suspicious_function(
let diagnostic = Diagnostic::new(diagnostic_kind, range);
if checker.enabled(diagnostic.kind.rule()) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}
/// S308
pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &Decorator) {
pub(crate) fn suspicious_function_decorator(checker: &Checker, decorator: &Decorator) {
// In preview mode, references are handled collectively by `suspicious_function_reference`
if checker.settings.preview.is_disabled() {
suspicious_function(checker, &decorator.expression, None, decorator.range);

View File

@@ -351,7 +351,7 @@ impl Violation for SuspiciousPyghmiImport {
}
/// S401, S402, S403, S404, S405, S406, S407, S408, S409, S410, S411, S412, S413, S415
pub(crate) fn suspicious_imports(checker: &mut Checker, stmt: &Stmt) {
pub(crate) fn suspicious_imports(checker: &Checker, stmt: &Stmt) {
// Skip stub files.
if checker.source_type.is_stub() {
return;
@@ -602,13 +602,9 @@ pub(crate) fn suspicious_imports(checker: &mut Checker, stmt: &Stmt) {
};
}
fn check_and_push_diagnostic(
checker: &mut Checker,
diagnostic_kind: DiagnosticKind,
range: TextRange,
) {
fn check_and_push_diagnostic(checker: &Checker, diagnostic_kind: DiagnosticKind, range: TextRange) {
let diagnostic = Diagnostic::new::<DiagnosticKind>(diagnostic_kind, range);
if checker.enabled(diagnostic.kind.rule()) {
checker.diagnostics.push(diagnostic);
checker.report_diagnostic(diagnostic);
}
}

Some files were not shown because too many files have changed in this diff Show More