Compare commits
20 Commits
0.9.5
...
david/comp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e5358de2b | ||
|
|
1f3ff48b4f | ||
|
|
5e027a43ff | ||
|
|
22728808aa | ||
|
|
a04ddf2a55 | ||
|
|
3a806ecaa1 | ||
|
|
a29009e4ed | ||
|
|
19f3424a1a | ||
|
|
d4a5772d96 | ||
|
|
efa8a3ddcc | ||
|
|
46fe17767d | ||
|
|
1f7a29d347 | ||
|
|
618bfaf884 | ||
|
|
b1c61cb2ee | ||
|
|
97e6fc3793 | ||
|
|
38351e00ee | ||
|
|
26c37b1e0e | ||
|
|
7db5a924af | ||
|
|
349f93389e | ||
|
|
bb979e05ac |
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(),
|
||||
|
||||
@@ -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 {
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(),
|
||||
|
||||
72
crates/ruff_dev/src/generate_knot_schema.rs
Normal file
72
crates/ruff_dev/src/generate_knot_schema.rs
Normal 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(¤t, &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 })
|
||||
}
|
||||
}
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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
|
||||
|
||||
0
crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/utils/__init__.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/utils/__init__.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/utils/logging.py
vendored
Normal file
0
crates/ruff_linter/resources/test/fixtures/flake8_builtins/A005/modules/utils/logging.py
vendored
Normal file
28
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417_1.py
vendored
Normal file
28
crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417_1.py
vendored
Normal 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 := [])))
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
###
|
||||
|
||||
@@ -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("")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,3 +12,13 @@ x: TypeAlias = tuple[
|
||||
int, # preserved
|
||||
float,
|
||||
]
|
||||
|
||||
T: TypeAlias = ( # comment0
|
||||
# comment1
|
||||
int # comment2
|
||||
# comment3
|
||||
| # comment4
|
||||
# comment5
|
||||
str # comment6
|
||||
# comment7
|
||||
) # comment8
|
||||
|
||||
@@ -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', ...)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(¶m_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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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(¶meter.parameter, default) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user