Compare commits
1 Commits
charlie/de
...
collect_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f2979ed66 |
@@ -240,16 +240,12 @@ def foo(f=lambda x: print(x)):
|
||||
|
||||
from collections import abc
|
||||
from typing import Annotated, Dict, Optional, Sequence, Union, Set
|
||||
import typing_extensions
|
||||
|
||||
|
||||
def immutable_annotations(
|
||||
a: Sequence[int] | None = [],
|
||||
b: Optional[abc.Mapping[int, int]] = {},
|
||||
c: Annotated[Union[abc.Set[str], abc.Sized], "annotation"] = set(),
|
||||
d: typing_extensions.Annotated[
|
||||
Union[abc.Set[str], abc.Sized], "annotation"
|
||||
] = set(),
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -258,6 +254,5 @@ def mutable_annotations(
|
||||
a: list[int] | None = [],
|
||||
b: Optional[Dict[int, int]] = {},
|
||||
c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -3,6 +3,3 @@ import logging
|
||||
name = "world"
|
||||
logging.info(f"Hello {name}")
|
||||
logging.log(logging.INFO, f"Hello {name}")
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
_LOGGER.info(f"{__name__}")
|
||||
|
||||
@@ -14,10 +14,6 @@ class BadClass:
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
@@ -14,10 +14,6 @@ class BadClass:
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"execution_count": null,
|
||||
"cell_type": "code",
|
||||
"id": "1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": ["%%timeit\n", "print('hello world')"]
|
||||
}
|
||||
@@ -25,23 +25,6 @@
|
||||
"def foo():\n",
|
||||
" pass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "16214f6f-bb32-4594-81be-79fb27c6ec92",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pathlib import Path\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"%matplotlib \\\n",
|
||||
" --inline\n",
|
||||
"\n",
|
||||
"import math\n",
|
||||
"import abc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -27,23 +27,6 @@
|
||||
"def foo():\n",
|
||||
" pass"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6d6c55c6-4a34-4662-914b-4ee11c9c24a5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"from pathlib import Path\n",
|
||||
"\n",
|
||||
"%matplotlib \\\n",
|
||||
" --inline\n",
|
||||
"\n",
|
||||
"import abc\n",
|
||||
"import math"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "eab4754a-d6df-4b41-8ee8-7e23aef440f9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"\n",
|
||||
"%matplotlib inline\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"_ = math.pi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2b0e2986-1b87-4bb6-9b1d-c11ca1decd87",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%timeit\n",
|
||||
"import sys"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cad32845-44f9-4a53-8b8c-a6b1bb3f3378",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import math\n",
|
||||
"\n",
|
||||
"%matplotlib inline\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_ = math.pi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d7b8e967-8b4a-493b-b6f7-d5cecfb3a5c3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%timeit\n",
|
||||
"import sys"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a0efffbc-85f1-4513-bf49-5387ec3a2a4e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" foo1 = %matplotlib --list\n",
|
||||
" foo2: list[str] = %matplotlib --list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "6e0b2b50-43f2-4f59-951d-9404dd560ae4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" bar1 = !pwd\n",
|
||||
" bar2: str = !pwd"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "24426ef2-046c-453e-b809-05b56e7355e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" %matplotlib --list\n",
|
||||
" %matplotlib --list"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3d98fdae-b86b-476e-b4db-9d3ce5562682",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def f():\n",
|
||||
" !pwd\n",
|
||||
" !pwd"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python (ruff)",
|
||||
"language": "python",
|
||||
"name": "ruff"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -634,8 +634,3 @@ def starts_with_this():
|
||||
@expect('D404: First word of the docstring should not be "This"')
|
||||
def starts_with_space_then_this():
|
||||
""" This is a docstring that starts with a space.""" # noqa: D210
|
||||
|
||||
|
||||
class SameLine: """This is a docstring on the same line"""
|
||||
|
||||
def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
@@ -19,10 +19,6 @@ foo in foo
|
||||
|
||||
foo not in foo
|
||||
|
||||
id(foo) == id(foo)
|
||||
|
||||
len(foo) == len(foo)
|
||||
|
||||
# Non-errors.
|
||||
"foo" == "foo" # This is flagged by `comparison-of-constant` instead.
|
||||
|
||||
@@ -47,11 +43,3 @@ foo is not bar
|
||||
foo in bar
|
||||
|
||||
foo not in bar
|
||||
|
||||
x(foo) == y(foo)
|
||||
|
||||
id(foo) == id(bar)
|
||||
|
||||
id(foo, bar) == id(foo, bar)
|
||||
|
||||
id(foo, bar=1) == id(foo, bar=1)
|
||||
|
||||
@@ -106,7 +106,3 @@ print('Hello %(arg)s' % bar['bop'])
|
||||
"""
|
||||
% (x,)
|
||||
)
|
||||
|
||||
"%s" % (
|
||||
x, # comment
|
||||
)
|
||||
|
||||
@@ -198,7 +198,3 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
).format(a=1)
|
||||
|
||||
"{}".format(**c)
|
||||
|
||||
"{}".format(
|
||||
1 # comment
|
||||
)
|
||||
|
||||
@@ -46,35 +46,6 @@ from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, Con
|
||||
if True: from collections import (
|
||||
Mapping, Counter)
|
||||
|
||||
# Bad imports from PYI027 that are now handled by PYI022 (UP035)
|
||||
from typing import ContextManager
|
||||
from typing import OrderedDict
|
||||
from typing_extensions import OrderedDict
|
||||
from typing import Callable
|
||||
from typing import ByteString
|
||||
from typing import Container
|
||||
from typing import Hashable
|
||||
from typing import ItemsView
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import KeysView
|
||||
from typing import Mapping
|
||||
from typing import MappingView
|
||||
from typing import MutableMapping
|
||||
from typing import MutableSequence
|
||||
from typing import MutableSet
|
||||
from typing import Sequence
|
||||
from typing import Sized
|
||||
from typing import ValuesView
|
||||
from typing import Awaitable
|
||||
from typing import AsyncIterator
|
||||
from typing import AsyncIterable
|
||||
from typing import Coroutine
|
||||
from typing import Collection
|
||||
from typing import AsyncGenerator
|
||||
from typing import Reversible
|
||||
from typing import Generator
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ impl<'a, T: Codegen<'a>> CodegenStylist<'a> for T {
|
||||
///
|
||||
/// Returns `Ok(None)` if the statement is empty after removing the imports.
|
||||
pub(crate) fn remove_imports<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
@@ -45,20 +45,27 @@ pub(crate) fn remove_imports<'a>(
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let aliases = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
names
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else if let ImportNames::Star(..) = &import_body.names {
|
||||
// Special-case: if the import is a `from ... import *`, then we delete the
|
||||
// entire statement.
|
||||
let mut found_star = false;
|
||||
for member in member_names {
|
||||
if member == "*" {
|
||||
for import in imports {
|
||||
let qualified_name = match import_body.module.as_ref() {
|
||||
Some(module_name) => format!("{}.*", compose_module_path(module_name)),
|
||||
None => "*".to_string(),
|
||||
};
|
||||
if import == qualified_name {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", member);
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", import);
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
@@ -75,10 +82,30 @@ pub(crate) fn remove_imports<'a>(
|
||||
// Preserve the trailing comma (or not) from the last entry.
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
for member in member_names {
|
||||
let alias_index = aliases
|
||||
.iter()
|
||||
.position(|alias| member == compose_module_path(&alias.name));
|
||||
for import in imports {
|
||||
let alias_index = aliases.iter().position(|alias| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == import
|
||||
});
|
||||
|
||||
if let Some(index) = alias_index {
|
||||
aliases.remove(index);
|
||||
}
|
||||
@@ -112,7 +139,7 @@ pub(crate) fn remove_imports<'a>(
|
||||
///
|
||||
/// Returns the modified import statement.
|
||||
pub(crate) fn retain_imports(
|
||||
member_names: &[&str],
|
||||
imports: &[&str],
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
@@ -124,11 +151,14 @@ pub(crate) fn retain_imports(
|
||||
bail!("Expected Statement::Simple");
|
||||
};
|
||||
|
||||
let aliases = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => &mut import_body.names,
|
||||
let (aliases, import_module) = match body.body.first_mut() {
|
||||
Some(SmallStatement::Import(import_body)) => (&mut import_body.names, None),
|
||||
Some(SmallStatement::ImportFrom(import_body)) => {
|
||||
if let ImportNames::Aliases(names) = &mut import_body.names {
|
||||
names
|
||||
(
|
||||
names,
|
||||
Some((&import_body.relative, import_body.module.as_ref())),
|
||||
)
|
||||
} else {
|
||||
bail!("Expected: ImportNames::Aliases");
|
||||
}
|
||||
@@ -140,9 +170,28 @@ pub(crate) fn retain_imports(
|
||||
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
|
||||
|
||||
aliases.retain(|alias| {
|
||||
member_names
|
||||
.iter()
|
||||
.any(|member| *member == compose_module_path(&alias.name))
|
||||
imports.iter().any(|import| {
|
||||
let qualified_name = match import_module {
|
||||
Some((relative, module)) => {
|
||||
let module = module.map(compose_module_path);
|
||||
let member = compose_module_path(&alias.name);
|
||||
let mut qualified_name = String::with_capacity(
|
||||
relative.len() + module.as_ref().map_or(0, String::len) + member.len() + 1,
|
||||
);
|
||||
for _ in 0..relative.len() {
|
||||
qualified_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
qualified_name.push_str(&module);
|
||||
qualified_name.push('.');
|
||||
}
|
||||
qualified_name.push_str(&member);
|
||||
qualified_name
|
||||
}
|
||||
None => compose_module_path(&alias.name),
|
||||
};
|
||||
qualified_name == *import
|
||||
})
|
||||
});
|
||||
|
||||
// But avoid destroying any trailing comments.
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, ExceptHandler, Expr, Keyword, PySourceType, Ranged, Stmt,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{lexer, AsMode};
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
@@ -58,14 +56,14 @@ pub(crate) fn delete_stmt(
|
||||
|
||||
/// Generate a `Fix` to remove the specified imports from an `import` statement.
|
||||
pub(crate) fn remove_unused_imports<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
unused_imports: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
parent: Option<&Stmt>,
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
indexer: &Indexer,
|
||||
) -> Result<Edit> {
|
||||
match codemods::remove_imports(member_names, stmt, locator, stylist)? {
|
||||
match codemods::remove_imports(unused_imports, stmt, locator, stylist)? {
|
||||
None => Ok(delete_stmt(stmt, parent, locator, indexer)),
|
||||
Some(content) => Ok(Edit::range_replacement(content, stmt.range())),
|
||||
}
|
||||
@@ -90,7 +88,6 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
if arguments.keywords.len() + arguments.args.len() > 1 {
|
||||
@@ -109,7 +106,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
@@ -138,7 +135,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
// previous comma to the end of the argument.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
source_type.as_mode(),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
|
||||
@@ -16,7 +16,7 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||
})
|
||||
| Stmt::AsyncFor(ast::StmtAsyncFor {
|
||||
target, iter, body, ..
|
||||
}) = &checker.semantic.current_statement()
|
||||
}) = &checker.semantic.stmt()
|
||||
{
|
||||
if checker.enabled(Rule::UnusedLoopControlVariable) {
|
||||
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||
// used at runtime, then by default, we avoid flagging any other
|
||||
// imports from that model as typing-only.
|
||||
let enforce_typing_imports = !checker.source_type.is_stub()
|
||||
let enforce_typing_imports = !checker.is_stub
|
||||
&& checker.any_enabled(&[
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
@@ -112,11 +112,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
@@ -172,14 +168,12 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source) = shadowed.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If this is an overloaded function, abort.
|
||||
if shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(checker.semantic.statement(source)),
|
||||
cast::decorator_list(
|
||||
checker.semantic.stmts[shadowed.source.unwrap()],
|
||||
),
|
||||
&checker.semantic,
|
||||
)
|
||||
{
|
||||
@@ -201,11 +195,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(
|
||||
left,
|
||||
right,
|
||||
checker.semantic.statements(),
|
||||
)
|
||||
branch_detection::different_forks(left, right, &checker.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
@@ -253,7 +243,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
pyflakes::rules::unused_annotation(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnusedClassMethodArgument,
|
||||
Rule::UnusedFunctionArgument,
|
||||
|
||||
@@ -30,7 +30,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::MissingTypeKwargs,
|
||||
Rule::MissingTypeSelf,
|
||||
]);
|
||||
let enforce_stubs = checker.source_type.is_stub() && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs = checker.is_stub && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
|
||||
let enforce_docstrings = checker.any_enabled(&[
|
||||
Rule::BlankLineAfterLastSection,
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if let Some(operator) = typing::to_pep604_operator(value, slice, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -44,7 +44,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP604Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py310
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
|
||||
// Ex) list[...]
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
@@ -84,7 +84,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
// traverse nested unions.
|
||||
let is_unchecked_union = checker
|
||||
.semantic
|
||||
.current_expression_grandparent()
|
||||
.expr_grandparent()
|
||||
.and_then(Expr::as_subscript_expr)
|
||||
.map_or(true, |parent| {
|
||||
!checker.semantic.match_typing_expr(&parent.value, "Union")
|
||||
@@ -176,7 +176,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
typing::to_pep585_generic(expr, &checker.semantic)
|
||||
{
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -187,7 +187,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP585Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -206,16 +206,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||
if checker.semantic.current_scope().kind.is_any_function() {
|
||||
if checker.semantic.scope().kind.is_any_function() {
|
||||
// Ignore globals.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scope()
|
||||
.get(id)
|
||||
.is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
})
|
||||
{
|
||||
if !checker.semantic.scope().get(id).is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
}) {
|
||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||
checker, expr, id,
|
||||
);
|
||||
@@ -224,7 +219,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
&checker.semantic.current_scope().kind
|
||||
&checker.semantic.scope().kind
|
||||
{
|
||||
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
||||
checker,
|
||||
@@ -235,7 +230,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
|
||||
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
if matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
||||
pep8_naming::rules::mixed_case_variable_in_global_scope(
|
||||
checker, expr, id,
|
||||
);
|
||||
@@ -248,7 +243,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_attribute_shadowing(
|
||||
checker, class_def, id, *range,
|
||||
@@ -277,7 +272,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
]) {
|
||||
if let Some(replacement) = typing::to_pep585_generic(expr, &checker.semantic) {
|
||||
if checker.enabled(Rule::FutureRewritableTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py39
|
||||
&& checker.settings.target_version >= PythonVersion::Py37
|
||||
&& !checker.semantic.future_annotations()
|
||||
@@ -290,7 +285,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP585Annotation) {
|
||||
if checker.source_type.is_stub()
|
||||
if checker.is_stub
|
||||
|| checker.settings.target_version >= PythonVersion::Py39
|
||||
|| (checker.settings.target_version >= PythonVersion::Py37
|
||||
&& checker.semantic.future_annotations()
|
||||
@@ -346,8 +341,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
// pylint
|
||||
Rule::BadStringFormatCharacter,
|
||||
// pyflakes
|
||||
Rule::StringDotFormatInvalidFormat,
|
||||
Rule::StringDotFormatExtraNamedArguments,
|
||||
@@ -673,7 +666,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_comprehensions::rules::unnecessary_map(
|
||||
checker,
|
||||
expr,
|
||||
checker.semantic.current_expression_parent(),
|
||||
checker.semantic.expr_parent(),
|
||||
func,
|
||||
args,
|
||||
);
|
||||
@@ -1071,7 +1064,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}) => {
|
||||
// Ex) `str | None`
|
||||
if checker.enabled(Rule::FutureRequiredTypeAnnotation) {
|
||||
if !checker.source_type.is_stub()
|
||||
if !checker.is_stub
|
||||
&& checker.settings.target_version < PythonVersion::Py310
|
||||
&& !checker.semantic.future_annotations()
|
||||
&& checker.semantic.in_annotation()
|
||||
@@ -1087,7 +1080,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
// Avoid duplicate checks if the parent is an `|` since these rules
|
||||
// traverse nested unions.
|
||||
let is_unchecked_union = !matches!(
|
||||
checker.semantic.current_expression_parent(),
|
||||
checker.semantic.expr_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
..
|
||||
@@ -1217,7 +1210,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
if checker.is_stub && checker.enabled(Rule::NumericLiteralTooLong) {
|
||||
flake8_pyi::rules::numeric_literal_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
@@ -1226,7 +1219,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
kind: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
if checker.is_stub && checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
}
|
||||
@@ -1254,7 +1247,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::StringOrBytesTooLong) {
|
||||
flake8_pyi::rules::string_or_bytes_too_long(checker, expr);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
|
||||
ruff::rules::implicit_optional(checker, parameters);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::BreakOutsideLoop) {
|
||||
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
&mut checker.semantic.parents().skip(1),
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ContinueOutsideLoop) {
|
||||
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
&mut checker.semantic.parents().skip(1),
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -113,7 +113,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if let Some(diagnostic) =
|
||||
pep8_naming::rules::invalid_first_argument_name_for_class_method(
|
||||
checker,
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
@@ -125,7 +125,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
|
||||
checker,
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
parameters,
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::PassStatementStubBody) {
|
||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||
}
|
||||
@@ -168,14 +168,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
type_params.as_ref(),
|
||||
);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub()
|
||||
|| checker.settings.target_version >= PythonVersion::Py311
|
||||
{
|
||||
if checker.is_stub || checker.settings.target_version >= PythonVersion::Py311 {
|
||||
if checker.enabled(Rule::NoReturnArgumentAnnotationInStub) {
|
||||
flake8_pyi::rules::no_return_argument_annotation(checker, parameters);
|
||||
}
|
||||
@@ -193,7 +191,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
if checker.enabled(Rule::DunderFunctionName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
|
||||
checker.semantic.current_scope(),
|
||||
checker.semantic.scope(),
|
||||
stmt,
|
||||
name,
|
||||
&checker.settings.pep8_naming.ignore_names,
|
||||
@@ -348,7 +346,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::YieldInForLoop) {
|
||||
pyupgrade::rules::yield_in_for_loop(checker, stmt);
|
||||
}
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
|
||||
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
|
||||
if checker.enabled(Rule::BuiltinAttributeShadowing) {
|
||||
flake8_builtins::rules::builtin_method_shadowing(
|
||||
checker,
|
||||
@@ -414,7 +412,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
body,
|
||||
);
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::DjangoModelWithoutDunderStr) {
|
||||
flake8_django::rules::model_without_dunder_str(checker, class_def);
|
||||
}
|
||||
@@ -455,7 +453,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::AbstractBaseClassWithoutAbstractMethod,
|
||||
Rule::EmptyMethodWithoutAbstractDecorator,
|
||||
@@ -469,7 +467,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::PassStatementStubBody) {
|
||||
flake8_pyi::rules::pass_statement_stub_body(checker, body);
|
||||
}
|
||||
@@ -571,7 +569,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
alias,
|
||||
);
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
}
|
||||
@@ -746,7 +744,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::FutureAnnotationsInStub) {
|
||||
flake8_pyi::rules::from_future_import(checker, import_from);
|
||||
}
|
||||
@@ -766,7 +764,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
} else if &alias.name == "*" {
|
||||
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
|
||||
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
if !matches!(checker.semantic.scope().kind, ScopeKind::Module) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
||||
name: helpers::format_import_from(level, module),
|
||||
@@ -891,7 +889,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() {
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::UselessImportAlias) {
|
||||
pylint::rules::useless_import_alias(checker, alias);
|
||||
}
|
||||
@@ -982,7 +980,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_simplify::rules::nested_if_statements(
|
||||
checker,
|
||||
if_,
|
||||
checker.semantic.current_statement_parent(),
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::IfWithSameArms) {
|
||||
@@ -1004,7 +1002,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
tryceratops::rules::type_check_without_type_error(
|
||||
checker,
|
||||
if_,
|
||||
checker.semantic.current_statement_parent(),
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::OutdatedVersionBlock) {
|
||||
@@ -1015,7 +1013,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
Rule::PatchVersionComparison,
|
||||
@@ -1110,7 +1108,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker,
|
||||
stmt,
|
||||
body,
|
||||
checker.semantic.current_statement_parent(),
|
||||
checker.semantic.stmt_parent(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedefinedLoopName) {
|
||||
@@ -1327,7 +1325,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.settings.rules.enabled(Rule::TypeBivariance) {
|
||||
pylint::rules::type_bivariance(checker, value);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnprefixedTypeParam,
|
||||
Rule::AssignmentDefaultInStub,
|
||||
@@ -1338,7 +1336,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
// Ignore assignments in function bodies; those are covered by other rules.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.scopes()
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
if checker.enabled(Rule::UnprefixedTypeParam) {
|
||||
@@ -1397,13 +1395,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NonPEP695TypeAlias) {
|
||||
pyupgrade::rules::non_pep695_type_alias(checker, assign_stmt);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub {
|
||||
if let Some(value) = value {
|
||||
if checker.enabled(Rule::AssignmentDefaultInStub) {
|
||||
// Ignore assignments in function bodies; those are covered by other rules.
|
||||
if !checker
|
||||
.semantic
|
||||
.current_scopes()
|
||||
.scopes()
|
||||
.any(|scope| scope.kind.is_any_function())
|
||||
{
|
||||
flake8_pyi::rules::annotated_assignment_default_in_stub(
|
||||
|
||||
@@ -39,13 +39,11 @@ use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, IsolationLevel};
|
||||
use ruff_python_ast::all::{extract_all_names, DunderAllFlags};
|
||||
use ruff_python_ast::helpers::{
|
||||
collect_import_from_member, extract_handled_exceptions, to_module_path,
|
||||
};
|
||||
use ruff_python_ast::helpers::{extract_handled_exceptions, to_module_path};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::str::trailing_quote;
|
||||
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
|
||||
use ruff_python_ast::{helpers, str, visitor, PySourceType};
|
||||
use ruff_python_ast::{helpers, str, visitor};
|
||||
use ruff_python_codegen::{Generator, Quote, Stylist};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
|
||||
@@ -55,6 +53,7 @@ use ruff_python_semantic::{
|
||||
ModuleKind, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::checkers::ast::deferred::Deferred;
|
||||
@@ -76,8 +75,8 @@ pub(crate) struct Checker<'a> {
|
||||
package: Option<&'a Path>,
|
||||
/// The module representation of the current file (e.g., `foo.bar`).
|
||||
module_path: Option<&'a [String]>,
|
||||
/// The [`PySourceType`] of the current file.
|
||||
pub(crate) source_type: PySourceType,
|
||||
/// Whether the current file is a stub (`.pyi`) file.
|
||||
is_stub: bool,
|
||||
/// The [`flags::Noqa`] for the current analysis (i.e., whether to respect suppression
|
||||
/// comments).
|
||||
noqa: flags::Noqa,
|
||||
@@ -119,7 +118,6 @@ impl<'a> Checker<'a> {
|
||||
stylist: &'a Stylist,
|
||||
indexer: &'a Indexer,
|
||||
importer: Importer<'a>,
|
||||
source_type: PySourceType,
|
||||
) -> Checker<'a> {
|
||||
Checker {
|
||||
settings,
|
||||
@@ -128,7 +126,7 @@ impl<'a> Checker<'a> {
|
||||
path,
|
||||
package,
|
||||
module_path: module.path(),
|
||||
source_type,
|
||||
is_stub: is_python_stub_file(path),
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
@@ -182,7 +180,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
// Find the quote character used to start the containing f-string.
|
||||
let expr = model.current_expression()?;
|
||||
let expr = model.expr()?;
|
||||
let string_range = self.indexer.f_string_range(expr.start())?;
|
||||
let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
|
||||
|
||||
@@ -202,7 +200,7 @@ impl<'a> Checker<'a> {
|
||||
/// thus be applied whenever we delete a statement, but can otherwise be omitted.
|
||||
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
|
||||
parent
|
||||
.and_then(|stmt| self.semantic.statement_id(stmt))
|
||||
.and_then(|stmt| self.semantic.stmts.node_id(stmt))
|
||||
.map_or(IsolationLevel::default(), |node_id| {
|
||||
IsolationLevel::Group(node_id.into())
|
||||
})
|
||||
@@ -235,6 +233,11 @@ impl<'a> Checker<'a> {
|
||||
&self.semantic
|
||||
}
|
||||
|
||||
/// Return `true` if the current file is a stub file (`.pyi`).
|
||||
pub(crate) const fn is_stub(&self) -> bool {
|
||||
self.is_stub
|
||||
}
|
||||
|
||||
/// The [`Path`] to the file under analysis.
|
||||
pub(crate) const fn path(&self) -> &'a Path {
|
||||
self.path
|
||||
@@ -264,7 +267,7 @@ where
|
||||
{
|
||||
fn visit_stmt(&mut self, stmt: &'b Stmt) {
|
||||
// Step 0: Pre-processing
|
||||
self.semantic.push_statement(stmt);
|
||||
self.semantic.push_stmt(stmt);
|
||||
|
||||
// Track whether we've seen docstrings, non-imports, etc.
|
||||
match stmt {
|
||||
@@ -288,7 +291,7 @@ where
|
||||
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
|
||||
if !self.semantic.seen_import_boundary()
|
||||
&& !helpers::is_assignment_to_a_dunder(stmt)
|
||||
&& !helpers::in_nested_block(self.semantic.current_statements())
|
||||
&& !helpers::in_nested_block(self.semantic.parents())
|
||||
{
|
||||
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
|
||||
}
|
||||
@@ -322,11 +325,11 @@ where
|
||||
// Given `import foo.bar`, `name` would be "foo", and `qualified_name` would be
|
||||
// "foo.bar".
|
||||
let name = alias.name.split('.').next().unwrap();
|
||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { call_path }),
|
||||
BindingKind::SubmoduleImport(SubmoduleImport { qualified_name }),
|
||||
BindingFlags::EXTERNAL,
|
||||
);
|
||||
} else {
|
||||
@@ -343,11 +346,11 @@ where
|
||||
}
|
||||
|
||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||
let call_path: Box<[&str]> = alias.name.split('.').collect();
|
||||
let qualified_name = &alias.name;
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::Import(Import { call_path }),
|
||||
BindingKind::Import(Import { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
}
|
||||
@@ -372,7 +375,7 @@ where
|
||||
);
|
||||
} else if &alias.name == "*" {
|
||||
self.semantic
|
||||
.current_scope_mut()
|
||||
.scope_mut()
|
||||
.add_star_import(StarImport { level, module });
|
||||
} else {
|
||||
let mut flags = BindingFlags::EXTERNAL;
|
||||
@@ -391,16 +394,12 @@ where
|
||||
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
|
||||
// and `qualified_name` would be "foo.bar".
|
||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||
|
||||
// Attempt to resolve any relative imports; but if we don't know the current
|
||||
// module path, or the relative import extends beyond the package root,
|
||||
// fallback to a literal representation (e.g., `[".", "foo"]`).
|
||||
let call_path = collect_import_from_member(level, module, &alias.name)
|
||||
.into_boxed_slice();
|
||||
let qualified_name =
|
||||
helpers::format_import_from_member(level, module, &alias.name);
|
||||
self.add_binding(
|
||||
name,
|
||||
alias.identifier(),
|
||||
BindingKind::FromImport(FromImport { call_path }),
|
||||
BindingKind::FromImport(FromImport { qualified_name }),
|
||||
flags,
|
||||
);
|
||||
}
|
||||
@@ -421,7 +420,7 @@ where
|
||||
BindingKind::Global,
|
||||
BindingFlags::GLOBAL,
|
||||
);
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(name, binding_id);
|
||||
}
|
||||
}
|
||||
@@ -444,7 +443,7 @@ where
|
||||
BindingKind::Nonlocal(scope_id),
|
||||
BindingFlags::NONLOCAL,
|
||||
);
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(name, binding_id);
|
||||
}
|
||||
}
|
||||
@@ -657,7 +656,7 @@ where
|
||||
// available at runtime.
|
||||
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
|
||||
let runtime_annotation = if self.semantic.future_annotations() {
|
||||
if self.semantic.current_scope().kind.is_class() {
|
||||
if self.semantic.scope().kind.is_class() {
|
||||
let baseclasses = &self
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
@@ -676,7 +675,7 @@ where
|
||||
}
|
||||
} else {
|
||||
matches!(
|
||||
self.semantic.current_scope().kind,
|
||||
self.semantic.scope().kind,
|
||||
ScopeKind::Class(_) | ScopeKind::Module
|
||||
)
|
||||
};
|
||||
@@ -777,7 +776,7 @@ where
|
||||
analyze::statement(stmt, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_statement();
|
||||
self.semantic.pop_stmt();
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'b Expr) {
|
||||
@@ -813,7 +812,7 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
self.semantic.push_expression(expr);
|
||||
self.semantic.push_expr(expr);
|
||||
|
||||
// Store the flags prior to any further descent, so that we can restore them after visiting
|
||||
// the node.
|
||||
@@ -841,7 +840,7 @@ where
|
||||
}) => {
|
||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||
if id == "locals" && ctx.is_load() {
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.set_uses_locals();
|
||||
}
|
||||
}
|
||||
@@ -1231,7 +1230,7 @@ where
|
||||
analyze::expression(expr, self);
|
||||
|
||||
self.semantic.flags = flags_snapshot;
|
||||
self.semantic.pop_expression();
|
||||
self.semantic.pop_expr();
|
||||
}
|
||||
|
||||
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
|
||||
@@ -1611,7 +1610,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
let parent = self.semantic.current_statement();
|
||||
let parent = self.semantic.stmt();
|
||||
|
||||
if matches!(
|
||||
parent,
|
||||
@@ -1646,7 +1645,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope = self.semantic.current_scope();
|
||||
let scope = self.semantic.scope();
|
||||
|
||||
if scope.kind.is_module()
|
||||
&& match parent {
|
||||
@@ -1698,8 +1697,8 @@ impl<'a> Checker<'a> {
|
||||
|
||||
if self
|
||||
.semantic
|
||||
.current_expressions()
|
||||
.any(Expr::is_named_expr_expr)
|
||||
.expr_ancestors()
|
||||
.any(|expr| expr.is_named_expr_expr())
|
||||
{
|
||||
self.add_binding(
|
||||
id,
|
||||
@@ -1725,7 +1724,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.resolve_del(id, expr.range());
|
||||
|
||||
if helpers::on_conditional_branch(&mut self.semantic.current_statements()) {
|
||||
if helpers::on_conditional_branch(&mut self.semantic.parents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1733,7 +1732,7 @@ impl<'a> Checker<'a> {
|
||||
let binding_id =
|
||||
self.semantic
|
||||
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
|
||||
let scope = self.semantic.current_scope_mut();
|
||||
let scope = self.semantic.scope_mut();
|
||||
scope.add(id, binding_id);
|
||||
}
|
||||
|
||||
@@ -1787,7 +1786,7 @@ impl<'a> Checker<'a> {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
}
|
||||
}
|
||||
if self.source_type.is_stub() {
|
||||
if self.is_stub {
|
||||
if self.enabled(Rule::QuotedAnnotationInStub) {
|
||||
flake8_pyi::rules::quoted_annotation_in_stub(self, value, range);
|
||||
}
|
||||
@@ -1825,7 +1824,7 @@ impl<'a> Checker<'a> {
|
||||
for snapshot in deferred_functions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
match &self.semantic.current_statement() {
|
||||
match &self.semantic.stmt() {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
})
|
||||
@@ -1929,7 +1928,6 @@ pub(crate) fn check_ast(
|
||||
noqa: flags::Noqa,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
) -> Vec<Diagnostic> {
|
||||
let module_path = package.and_then(|package| to_module_path(package, path));
|
||||
let module = Module {
|
||||
@@ -1957,7 +1955,6 @@ pub(crate) fn check_ast(
|
||||
stylist,
|
||||
indexer,
|
||||
Importer::new(python_ast, locator, stylist),
|
||||
source_type,
|
||||
);
|
||||
checker.bind_builtins();
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
@@ -10,7 +10,7 @@ use ruff_python_ast::imports::{ImportMap, ModuleImport};
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::directives::IsortDirectives;
|
||||
@@ -87,12 +87,12 @@ pub(crate) fn check_imports(
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
let is_stub = is_python_stub_file(path);
|
||||
|
||||
// Extract all import blocks from the AST.
|
||||
let tracker = {
|
||||
let mut tracker =
|
||||
BlockBuilder::new(locator, directives, source_type.is_stub(), source_kind);
|
||||
let mut tracker = BlockBuilder::new(locator, directives, is_stub, source_kind);
|
||||
tracker.visit_body(python_ast);
|
||||
tracker
|
||||
};
|
||||
@@ -104,13 +104,7 @@ pub(crate) fn check_imports(
|
||||
for block in &blocks {
|
||||
if !block.imports.is_empty() {
|
||||
if let Some(diagnostic) = isort::rules::organize_imports(
|
||||
block,
|
||||
locator,
|
||||
stylist,
|
||||
indexer,
|
||||
settings,
|
||||
package,
|
||||
source_type,
|
||||
block, locator, stylist, indexer, settings, package,
|
||||
) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -119,11 +113,7 @@ pub(crate) fn check_imports(
|
||||
}
|
||||
if settings.rules.enabled(Rule::MissingRequiredImport) {
|
||||
diagnostics.extend(isort::rules::add_required_imports(
|
||||
python_ast,
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
python_ast, locator, stylist, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Insert statements into Python code.
|
||||
use std::ops::Add;
|
||||
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -137,7 +137,6 @@ impl<'a> Insertion<'a> {
|
||||
mut location: TextSize,
|
||||
locator: &Locator<'a>,
|
||||
stylist: &Stylist,
|
||||
source_type: PySourceType,
|
||||
) -> Insertion<'a> {
|
||||
enum Awaiting {
|
||||
Colon(u32),
|
||||
@@ -147,7 +146,7 @@ impl<'a> Insertion<'a> {
|
||||
|
||||
let mut state = Awaiting::Colon(0);
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(locator.after(location), source_type.as_mode(), location).flatten()
|
||||
lexer::lex_starts_at(locator.after(location), Mode::Module, location).flatten()
|
||||
{
|
||||
match state {
|
||||
// Iterate until we find the colon indicating the start of the block body.
|
||||
@@ -301,20 +300,20 @@ fn match_leading_semicolon(s: &str) -> Option<TextSize> {
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{parse_suite, Mode};
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use super::Insertion;
|
||||
|
||||
#[test]
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Ok(Insertion::start_of_file(&program, &locator, &stylist))
|
||||
@@ -425,10 +424,10 @@ x = 1
|
||||
#[test]
|
||||
fn start_of_block() {
|
||||
fn insert(contents: &str, offset: TextSize) -> Insertion {
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, Mode::Module);
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
Insertion::start_of_block(offset, &locator, &stylist, PySourceType::default())
|
||||
Insertion::start_of_block(offset, &locator, &stylist)
|
||||
}
|
||||
|
||||
let contents = "if True: pass";
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
use libcst_native::{ImportAlias, Name, NameOrAttribute};
|
||||
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt, Suite};
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
@@ -87,13 +87,13 @@ impl<'a> Importer<'a> {
|
||||
/// import statement.
|
||||
pub(crate) fn runtime_import_edit(
|
||||
&self,
|
||||
import: &ImportedMembers,
|
||||
import: &StmtImports,
|
||||
at: TextSize,
|
||||
) -> Result<RuntimeImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
&import.qualified_names,
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
@@ -118,15 +118,14 @@ impl<'a> Importer<'a> {
|
||||
/// `TYPE_CHECKING` block.
|
||||
pub(crate) fn typing_import_edit(
|
||||
&self,
|
||||
import: &ImportedMembers,
|
||||
import: &StmtImports,
|
||||
at: TextSize,
|
||||
semantic: &SemanticModel,
|
||||
source_type: PySourceType,
|
||||
) -> Result<TypingImportEdit> {
|
||||
// Generate the modified import statement.
|
||||
let content = autofix::codemods::retain_imports(
|
||||
&import.names,
|
||||
import.statement,
|
||||
&import.qualified_names,
|
||||
import.stmt,
|
||||
self.locator,
|
||||
self.stylist,
|
||||
)?;
|
||||
@@ -141,7 +140,7 @@ impl<'a> Importer<'a> {
|
||||
// Add the import to a `TYPE_CHECKING` block.
|
||||
let add_import_edit = if let Some(block) = self.preceding_type_checking_block(at) {
|
||||
// Add the import to the `TYPE_CHECKING` block.
|
||||
self.add_to_type_checking_block(&content, block.start(), source_type)
|
||||
self.add_to_type_checking_block(&content, block.start())
|
||||
} else {
|
||||
// Add the import to a new `TYPE_CHECKING` block.
|
||||
self.add_type_checking_block(
|
||||
@@ -354,13 +353,8 @@ impl<'a> Importer<'a> {
|
||||
}
|
||||
|
||||
/// Add an import statement to an existing `TYPE_CHECKING` block.
|
||||
fn add_to_type_checking_block(
|
||||
&self,
|
||||
content: &str,
|
||||
at: TextSize,
|
||||
source_type: PySourceType,
|
||||
) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist, source_type).into_edit(content)
|
||||
fn add_to_type_checking_block(&self, content: &str, at: TextSize) -> Edit {
|
||||
Insertion::start_of_block(at, self.locator, self.stylist).into_edit(content)
|
||||
}
|
||||
|
||||
/// Return the import statement that precedes the given position, if any.
|
||||
@@ -452,11 +446,11 @@ impl<'a> ImportRequest<'a> {
|
||||
}
|
||||
|
||||
/// An existing list of module or member imports, located within an import statement.
|
||||
pub(crate) struct ImportedMembers<'a> {
|
||||
pub(crate) struct StmtImports<'a> {
|
||||
/// The import statement.
|
||||
pub(crate) statement: &'a Stmt,
|
||||
/// The "names" of the imported members.
|
||||
pub(crate) names: Vec<&'a str>,
|
||||
pub(crate) stmt: &'a Stmt,
|
||||
/// The "qualified names" of the imported modules or members.
|
||||
pub(crate) qualified_names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
/// The result of an [`Importer::get_or_import_symbol`] call.
|
||||
|
||||
@@ -24,6 +24,8 @@ use crate::IOError;
|
||||
|
||||
pub const JUPYTER_NOTEBOOK_EXT: &str = "ipynb";
|
||||
|
||||
const MAGIC_PREFIX: [&str; 3] = ["%", "!", "?"];
|
||||
|
||||
/// Run round-trip source code generation on a given Jupyter notebook file path.
|
||||
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
let mut notebook = Notebook::read(path).map_err(|err| {
|
||||
@@ -76,21 +78,26 @@ impl Cell {
|
||||
/// Return `true` if it's a valid code cell.
|
||||
///
|
||||
/// A valid code cell is a cell where the cell type is [`Cell::Code`] and the
|
||||
/// source doesn't contain a cell magic.
|
||||
/// source doesn't contain a magic, shell or help command.
|
||||
fn is_valid_code_cell(&self) -> bool {
|
||||
let source = match self {
|
||||
Cell::Code(cell) => &cell.source,
|
||||
_ => return false,
|
||||
};
|
||||
// Ignore cells containing cell magic. This is different from line magic
|
||||
// which is allowed and ignored by the parser.
|
||||
// Ignore a cell if it contains a magic command. There could be valid
|
||||
// Python code as well, but we'll ignore that for now.
|
||||
// TODO(dhruvmanila): https://github.com/psf/black/blob/main/src/black/handle_ipynb_magics.py
|
||||
!match source {
|
||||
SourceValue::String(string) => string
|
||||
.lines()
|
||||
.any(|line| line.trim_start().starts_with("%%")),
|
||||
SourceValue::StringArray(string_array) => string_array
|
||||
.iter()
|
||||
.any(|line| line.trim_start().starts_with("%%")),
|
||||
SourceValue::String(string) => string.lines().any(|line| {
|
||||
MAGIC_PREFIX
|
||||
.iter()
|
||||
.any(|prefix| line.trim_start().starts_with(prefix))
|
||||
}),
|
||||
SourceValue::StringArray(string_array) => string_array.iter().any(|line| {
|
||||
MAGIC_PREFIX
|
||||
.iter()
|
||||
.any(|prefix| line.trim_start().starts_with(prefix))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,10 +513,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Path::new("markdown.json"), false; "markdown")]
|
||||
#[test_case(Path::new("only_magic.json"), true; "only_magic")]
|
||||
#[test_case(Path::new("code_and_magic.json"), true; "code_and_magic")]
|
||||
#[test_case(Path::new("only_magic.json"), false; "only_magic")]
|
||||
#[test_case(Path::new("code_and_magic.json"), false; "code_and_magic")]
|
||||
#[test_case(Path::new("only_code.json"), true; "only_code")]
|
||||
#[test_case(Path::new("cell_magic.json"), false; "cell_magic")]
|
||||
fn test_is_valid_code_cell(path: &Path, expected: bool) -> Result<()> {
|
||||
assert_eq!(read_jupyter_cell(path)?.is_valid_code_cell(), expected);
|
||||
Ok(())
|
||||
@@ -561,7 +567,7 @@ print("after empty cells")
|
||||
#[test]
|
||||
fn test_import_sorting() -> Result<()> {
|
||||
let path = "isort.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
let (diagnostics, source_kind) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("isort_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnsortedImports),
|
||||
@@ -570,34 +576,10 @@ print("after empty cells")
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_magics() -> Result<()> {
|
||||
let path = "line_magics.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("line_magics_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unused_variable() -> Result<()> {
|
||||
let path = "unused_variable.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("unused_variable_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_consistency() -> Result<()> {
|
||||
let path = "before_fix.ipynb".to_string();
|
||||
let (_, _, source_kind) = test_notebook_path(
|
||||
let (_, source_kind) = test_notebook_path(
|
||||
path,
|
||||
Path::new("after_fix.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
|
||||
@@ -47,43 +47,4 @@ isort.ipynb:cell 2:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
7 9 | def foo():
|
||||
8 10 | pass
|
||||
|
||||
isort.ipynb:cell 3:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | / from pathlib import Path
|
||||
2 | | import sys
|
||||
3 | |
|
||||
4 | | %matplotlib \
|
||||
| |_^ I001
|
||||
5 | --inline
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
6 6 | # Newline should be added here
|
||||
7 7 | def foo():
|
||||
8 8 | pass
|
||||
9 |+import sys
|
||||
9 10 | from pathlib import Path
|
||||
10 |-import sys
|
||||
11 11 |
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
|
||||
isort.ipynb:cell 3:7:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
5 | --inline
|
||||
6 |
|
||||
7 | / import math
|
||||
8 | | import abc
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
14 14 |
|
||||
15 |+import abc
|
||||
15 16 | import math
|
||||
16 |-import abc
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
3 | %matplotlib inline
|
||||
4 |
|
||||
5 | import os
|
||||
| ^^ F401
|
||||
6 |
|
||||
7 | _ = math.pi
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
ℹ Fix
|
||||
2 2 |
|
||||
3 3 | %matplotlib inline
|
||||
4 4 |
|
||||
5 |-import os
|
||||
6 5 |
|
||||
7 6 | _ = math.pi
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
unused_variable.ipynb:cell 1:2:5: F841 [*] Local variable `foo1` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | foo1 = %matplotlib --list
|
||||
| ^^^^ F841
|
||||
3 | foo2: list[str] = %matplotlib --list
|
||||
|
|
||||
= help: Remove assignment to unused variable `foo1`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | def f():
|
||||
2 |- foo1 = %matplotlib --list
|
||||
2 |+ %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 1:3:5: F841 [*] Local variable `foo2` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | foo1 = %matplotlib --list
|
||||
3 | foo2: list[str] = %matplotlib --list
|
||||
| ^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `foo2`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | def f():
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 |- foo2: list[str] = %matplotlib --list
|
||||
3 |+ %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 2:2:5: F841 [*] Local variable `bar1` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | bar1 = !pwd
|
||||
| ^^^^ F841
|
||||
3 | bar2: str = !pwd
|
||||
|
|
||||
= help: Remove assignment to unused variable `bar1`
|
||||
|
||||
ℹ Suggested fix
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 |- bar1 = !pwd
|
||||
5 |+ !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
|
||||
unused_variable.ipynb:cell 2:3:5: F841 [*] Local variable `bar2` is assigned to but never used
|
||||
|
|
||||
1 | def f():
|
||||
2 | bar1 = !pwd
|
||||
3 | bar2: str = !pwd
|
||||
| ^^^^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `bar2`
|
||||
|
||||
ℹ Suggested fix
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 |- bar2: str = !pwd
|
||||
6 |+ !pwd
|
||||
|
||||
|
||||
@@ -7,15 +7,14 @@ use colored::Colorize;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{AsMode, ParseError};
|
||||
use ruff_python_parser::ParseError;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
|
||||
use ruff_python_stdlib::path::is_python_stub_file;
|
||||
use ruff_source_file::{Locator, SourceFileBuilder};
|
||||
|
||||
use crate::autofix::{fix_file, FixResult};
|
||||
@@ -82,7 +81,6 @@ pub fn check_path(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
@@ -103,13 +101,9 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_tokens())
|
||||
{
|
||||
let is_stub = is_python_stub_file(path);
|
||||
diagnostics.extend(check_tokens(
|
||||
&tokens,
|
||||
path,
|
||||
locator,
|
||||
indexer,
|
||||
settings,
|
||||
source_type.is_stub(),
|
||||
&tokens, path, locator, indexer, settings, is_stub,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -144,11 +138,7 @@ pub fn check_path(
|
||||
.iter_enabled()
|
||||
.any(|rule_code| rule_code.lint_source().is_imports());
|
||||
if use_ast || use_imports || use_doc_lines {
|
||||
match ruff_python_parser::parse_program_tokens(
|
||||
tokens,
|
||||
&path.to_string_lossy(),
|
||||
source_type.is_jupyter(),
|
||||
) {
|
||||
match ruff_python_parser::parse_program_tokens(tokens, &path.to_string_lossy()) {
|
||||
Ok(python_ast) => {
|
||||
if use_ast {
|
||||
diagnostics.extend(check_ast(
|
||||
@@ -161,7 +151,6 @@ pub fn check_path(
|
||||
noqa,
|
||||
path,
|
||||
package,
|
||||
source_type,
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
@@ -175,7 +164,6 @@ pub fn check_path(
|
||||
path,
|
||||
package,
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
imports = module_imports;
|
||||
diagnostics.extend(import_diagnostics);
|
||||
@@ -268,13 +256,11 @@ const MAX_ITERATIONS: usize = 100;
|
||||
|
||||
/// Add any missing `# noqa` pragmas to the source code at the given `Path`.
|
||||
pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings) -> Result<usize> {
|
||||
let source_type = PySourceType::from(path);
|
||||
|
||||
// Read the file from disk.
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&contents);
|
||||
@@ -308,7 +294,6 @@ pub fn add_noqa_to_path(path: &Path, package: Option<&Path>, settings: &Settings
|
||||
settings,
|
||||
flags::Noqa::Disabled,
|
||||
None,
|
||||
source_type,
|
||||
);
|
||||
|
||||
// Log any parse errors.
|
||||
@@ -341,10 +326,9 @@ pub fn lint_only(
|
||||
settings: &Settings,
|
||||
noqa: flags::Noqa,
|
||||
source_kind: Option<&SourceKind>,
|
||||
source_type: PySourceType,
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(contents);
|
||||
@@ -375,7 +359,6 @@ pub fn lint_only(
|
||||
settings,
|
||||
noqa,
|
||||
source_kind,
|
||||
source_type,
|
||||
);
|
||||
|
||||
result.map(|(diagnostics, imports)| {
|
||||
@@ -422,7 +405,6 @@ pub fn lint_fix<'a>(
|
||||
noqa: flags::Noqa,
|
||||
settings: &Settings,
|
||||
source_kind: &mut SourceKind,
|
||||
source_type: PySourceType,
|
||||
) -> Result<FixerResult<'a>> {
|
||||
let mut transformed = Cow::Borrowed(contents);
|
||||
|
||||
@@ -438,8 +420,7 @@ pub fn lint_fix<'a>(
|
||||
// Continuously autofix until the source code stabilizes.
|
||||
loop {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> =
|
||||
ruff_python_parser::tokenize(&transformed, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&transformed);
|
||||
|
||||
// Map row and column locations to byte slices (lazily).
|
||||
let locator = Locator::new(&transformed);
|
||||
@@ -470,7 +451,6 @@ pub fn lint_fix<'a>(
|
||||
settings,
|
||||
noqa,
|
||||
Some(source_kind),
|
||||
source_type,
|
||||
);
|
||||
|
||||
if iterations == 0 {
|
||||
|
||||
@@ -231,7 +231,7 @@ impl Renamer {
|
||||
}
|
||||
BindingKind::SubmoduleImport(import) => {
|
||||
// Ex) Rename `import pandas.core` to `import pandas as pd`.
|
||||
let module_name = import.call_path.first().unwrap();
|
||||
let module_name = import.qualified_name.split('.').next().unwrap();
|
||||
Some(Edit::range_replacement(
|
||||
format!("{module_name} as {target}"),
|
||||
binding.range,
|
||||
|
||||
@@ -262,6 +262,7 @@ pub fn python_files_in_path(
|
||||
builder.add(path);
|
||||
}
|
||||
builder.standard_filters(pyproject_config.settings.lib.respect_gitignore);
|
||||
builder.require_git(false);
|
||||
builder.hidden(false);
|
||||
let walker = builder.build_parallel();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_source_file::Locator;
|
||||
@@ -10,7 +10,6 @@ pub(crate) fn add_return_annotation(
|
||||
locator: &Locator,
|
||||
stmt: &Stmt,
|
||||
annotation: &str,
|
||||
source_type: PySourceType,
|
||||
) -> Result<Edit> {
|
||||
let contents = &locator.contents()[stmt.range()];
|
||||
|
||||
@@ -18,9 +17,7 @@ pub(crate) fn add_return_annotation(
|
||||
let mut seen_lpar = false;
|
||||
let mut seen_rpar = false;
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in
|
||||
lexer::lex_starts_at(contents, source_type.as_mode(), stmt.start()).flatten()
|
||||
{
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, stmt.start()).flatten() {
|
||||
if seen_lpar && seen_rpar {
|
||||
if matches!(tok, Tok::Colon) {
|
||||
return Ok(Edit::insertion(format!(" -> {annotation}"), range.start()));
|
||||
|
||||
@@ -709,13 +709,8 @@ pub(crate) fn definition(
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
checker.locator(),
|
||||
stmt,
|
||||
"None",
|
||||
checker.source_type,
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
fixes::add_return_annotation(checker.locator(), stmt, "None")
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
@@ -732,13 +727,8 @@ pub(crate) fn definition(
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if let Some(return_type) = simple_magic_return_type(name) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
fixes::add_return_annotation(
|
||||
checker.locator(),
|
||||
stmt,
|
||||
return_type,
|
||||
checker.source_type,
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
fixes::add_return_annotation(checker.locator(), stmt, return_type)
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool
|
||||
}) => {
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
if model
|
||||
.current_expression_parent()
|
||||
.expr_parent()
|
||||
.map_or(true, |parent| !parent.is_bin_op_expr())
|
||||
{
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
|
||||
@@ -10,31 +10,6 @@ use crate::{
|
||||
checkers::ast::Checker, registry::Rule, rules::flake8_bandit::helpers::string_literal,
|
||||
};
|
||||
|
||||
/// ## What it does
|
||||
/// Check for method calls that initiate a subprocess with a shell.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Starting a subprocess with a shell can allow attackers to execute arbitrary
|
||||
/// shell commands. Consider starting the process without a shell call and
|
||||
/// sanitize the input to mitigate the risk of shell injection.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.run("ls -l", shell=True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.run(["ls", "-l"])
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct SubprocessPopenWithShellEqualsTrue {
|
||||
seems_safe: bool,
|
||||
@@ -53,30 +28,6 @@ impl Violation for SubprocessPopenWithShellEqualsTrue {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Check for method calls that initiate a subprocess without a shell.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Starting a subprocess without a shell can prevent attackers from executing
|
||||
/// arbitrary shell commands; however, it is still error-prone. Consider
|
||||
/// validating the input.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Prone to false positives as it is difficult to determine whether the
|
||||
/// passed arguments have been validated ([#4045]).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// cmd = input("Enter a command: ").split()
|
||||
/// subprocess.run(cmd)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess` — Subprocess management](https://docs.python.org/3/library/subprocess.html)
|
||||
///
|
||||
/// [#4045]: https://github.com/astral-sh/ruff/issues/4045
|
||||
#[violation]
|
||||
pub struct SubprocessWithoutShellEqualsTrue;
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
|
||||
/// B019
|
||||
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
for decorator in decorator_list {
|
||||
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn setattr_with_constant(
|
||||
if let Stmt::Expr(ast::StmtExpr {
|
||||
value: child,
|
||||
range: _,
|
||||
}) = checker.semantic().current_statement()
|
||||
}) = checker.semantic().stmt()
|
||||
{
|
||||
if expr == child.as_ref() {
|
||||
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());
|
||||
|
||||
@@ -159,7 +159,7 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
|
||||
if certainty.into() {
|
||||
// Avoid fixing if the variable, or any future bindings to the variable, are
|
||||
// used _after_ the loop.
|
||||
let scope = checker.semantic().current_scope();
|
||||
let scope = checker.semantic().scope();
|
||||
if scope
|
||||
.get_all(name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
|
||||
@@ -81,43 +81,33 @@ B006_B008.py:221:20: B006 Do not use mutable data structures for argument defaul
|
||||
222 | pass
|
||||
|
|
||||
|
||||
B006_B008.py:258:27: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:254:27: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
| ^^ B006
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
|
|
||||
|
||||
B006_B008.py:259:35: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:255:35: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
257 | def mutable_annotations(
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
253 | def mutable_annotations(
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
| ^^ B006
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
257 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:260:62: B006 Do not use mutable data structures for argument defaults
|
||||
B006_B008.py:256:62: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
258 | a: list[int] | None = [],
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
254 | a: list[int] | None = [],
|
||||
255 | b: Optional[Dict[int, int]] = {},
|
||||
256 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
262 | ):
|
||||
|
|
||||
|
||||
B006_B008.py:261:80: B006 Do not use mutable data structures for argument defaults
|
||||
|
|
||||
259 | b: Optional[Dict[int, int]] = {},
|
||||
260 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
261 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
||||
| ^^^^^ B006
|
||||
262 | ):
|
||||
263 | pass
|
||||
257 | ):
|
||||
258 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use libcst_native::{
|
||||
RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
|
||||
TrailingWhitespace, Tuple,
|
||||
};
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
@@ -28,7 +28,7 @@ use crate::{
|
||||
pub(crate) fn fix_unnecessary_generator_list(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(GeneratorExp)))) -> Expr(ListComp)))
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -58,7 +58,10 @@ pub(crate) fn fix_unnecessary_generator_list(
|
||||
}
|
||||
|
||||
/// (C401) Convert `set(x for x in y)` to `{x for x in y}`.
|
||||
pub(crate) fn fix_unnecessary_generator_set(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_generator_set(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -93,7 +96,10 @@ pub(crate) fn fix_unnecessary_generator_set(checker: &Checker, expr: &Expr) -> R
|
||||
|
||||
/// (C402) Convert `dict((x, x) for x in range(3))` to `{x: x for x in
|
||||
/// range(3)}`.
|
||||
pub(crate) fn fix_unnecessary_generator_dict(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_generator_dict(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -135,7 +141,7 @@ pub(crate) fn fix_unnecessary_generator_dict(checker: &Checker, expr: &Expr) ->
|
||||
/// (C403) Convert `set([x for x in y])` to `{x for x in y}`.
|
||||
pub(crate) fn fix_unnecessary_list_comprehension_set(
|
||||
checker: &Checker,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
@@ -171,7 +177,7 @@ pub(crate) fn fix_unnecessary_list_comprehension_set(
|
||||
/// range(3)}`.
|
||||
pub(crate) fn fix_unnecessary_list_comprehension_dict(
|
||||
checker: &Checker,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
@@ -256,7 +262,10 @@ fn drop_trailing_comma<'a>(
|
||||
}
|
||||
|
||||
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
|
||||
pub(crate) fn fix_unnecessary_literal_set(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_literal_set(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -297,7 +306,10 @@ pub(crate) fn fix_unnecessary_literal_set(checker: &Checker, expr: &Expr) -> Res
|
||||
}
|
||||
|
||||
/// (C406) Convert `dict([(1, 2)])` to `{1: 2}`.
|
||||
pub(crate) fn fix_unnecessary_literal_dict(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_literal_dict(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let locator = checker.locator();
|
||||
let stylist = checker.stylist();
|
||||
|
||||
@@ -360,7 +372,10 @@ pub(crate) fn fix_unnecessary_literal_dict(checker: &Checker, expr: &Expr) -> Re
|
||||
}
|
||||
|
||||
/// (C408)
|
||||
pub(crate) fn fix_unnecessary_collection_call(checker: &Checker, expr: &Expr) -> Result<Edit> {
|
||||
pub(crate) fn fix_unnecessary_collection_call(
|
||||
checker: &Checker,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
enum Collection {
|
||||
Tuple,
|
||||
List,
|
||||
@@ -520,7 +535,7 @@ fn pad_expression(content: String, range: TextRange, checker: &Checker) -> Strin
|
||||
pub(crate) fn fix_unnecessary_literal_within_tuple_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -570,7 +585,7 @@ pub(crate) fn fix_unnecessary_literal_within_tuple_call(
|
||||
pub(crate) fn fix_unnecessary_literal_within_list_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -622,7 +637,7 @@ pub(crate) fn fix_unnecessary_literal_within_list_call(
|
||||
pub(crate) fn fix_unnecessary_list_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
// Expr(Call(List|Tuple)))) -> Expr(List|Tuple)))
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -644,7 +659,7 @@ pub(crate) fn fix_unnecessary_list_call(
|
||||
pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -756,7 +771,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -786,7 +801,7 @@ pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
pub(crate) fn fix_unnecessary_comprehension(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -873,8 +888,8 @@ pub(crate) fn fix_unnecessary_comprehension(
|
||||
pub(crate) fn fix_unnecessary_map(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
parent: Option<&Expr>,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
parent: Option<&ruff_python_ast::Expr>,
|
||||
object_type: ObjectType,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
@@ -1003,7 +1018,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
// If the expression is embedded in an f-string, surround it with spaces to avoid
|
||||
// syntax errors.
|
||||
if matches!(object_type, ObjectType::Set | ObjectType::Dict) {
|
||||
if parent.is_some_and(Expr::is_formatted_value_expr) {
|
||||
if parent.is_some_and(ruff_python_ast::Expr::is_formatted_value_expr) {
|
||||
content = format!(" {content} ");
|
||||
}
|
||||
}
|
||||
@@ -1018,7 +1033,7 @@ pub(crate) fn fix_unnecessary_map(
|
||||
pub(crate) fn fix_unnecessary_literal_within_dict_call(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Edit> {
|
||||
let module_text = locator.slice(expr.range());
|
||||
let mut tree = match_expression(module_text)?;
|
||||
@@ -1037,7 +1052,7 @@ pub(crate) fn fix_unnecessary_literal_within_dict_call(
|
||||
pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
expr: &Expr,
|
||||
expr: &ruff_python_ast::Expr,
|
||||
) -> Result<Fix> {
|
||||
// Expr(ListComp) -> Expr(GeneratorExp)
|
||||
let module_text = locator.slice(expr.range());
|
||||
|
||||
@@ -43,8 +43,8 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
|
||||
};
|
||||
|
||||
let (Some(grandparent), Some(parent)) = (
|
||||
checker.semantic().current_expression_grandparent(),
|
||||
checker.semantic().current_expression_parent(),
|
||||
checker.semantic().expr_grandparent(),
|
||||
checker.semantic().expr_parent(),
|
||||
) else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
CallDatetimeStrptimeWithoutZone,
|
||||
|
||||
@@ -18,8 +18,9 @@ use super::helpers;
|
||||
/// `datetime` objects are preferred, as they represent a specific moment in
|
||||
/// time, unlike "naive" objects.
|
||||
///
|
||||
/// `datetime.datetime.today()` creates a "naive" object; instead, use
|
||||
/// `datetime.datetime.now(tz=)` to create a timezone-aware object.
|
||||
/// `datetime.datetime.today()` crates a "naive" object; instead, use
|
||||
/// instead, use `datetime.datetime.now(tz=)` to create a timezone-aware
|
||||
/// object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -8,43 +8,6 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.utcfromtimestamp()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.utcfromtimestamp()` returns a naive datetime object;
|
||||
/// instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a
|
||||
/// timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.utcfromtimestamp()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeUtcfromtimestamp;
|
||||
|
||||
@@ -58,6 +21,15 @@ impl Violation for CallDatetimeUtcfromtimestamp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `datetime.datetime.utcfromtimestamp()`. (DTZ004)
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
///
|
||||
/// Because naive `datetime` objects are treated by many `datetime` methods as
|
||||
/// local times, it is preferred to use aware datetimes to represent times in
|
||||
/// UTC. As such, the recommended way to create an object representing a
|
||||
/// specific timestamp in UTC is by calling `datetime.fromtimestamp(timestamp,
|
||||
/// tz=timezone.utc)`.
|
||||
pub(crate) fn call_datetime_utcfromtimestamp(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
|
||||
@@ -8,42 +8,6 @@ use crate::checkers::ast::Checker;
|
||||
|
||||
use super::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for usage of `datetime.datetime.utcnow()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.datetime.utcnow()` returns a naive datetime object; instead, use
|
||||
/// `datetime.datetime.now(tz=)` to return a timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.utcnow()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[violation]
|
||||
pub struct CallDatetimeUtcnow;
|
||||
|
||||
@@ -57,6 +21,14 @@ impl Violation for CallDatetimeUtcnow {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for `datetime.datetime.today()`. (DTZ003)
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
///
|
||||
/// Because naive `datetime` objects are treated by many `datetime` methods as
|
||||
/// local times, it is preferred to use aware datetimes to represent times in
|
||||
/// UTC. As such, the recommended way to create an object representing the
|
||||
/// current time in UTC is by calling `datetime.now(timezone.utc)`.
|
||||
pub(crate) fn call_datetime_utcnow(checker: &mut Checker, func: &Expr, location: TextRange) {
|
||||
if !checker
|
||||
.semantic()
|
||||
|
||||
@@ -31,13 +31,6 @@ use super::helpers;
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
#[violation]
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
|
||||
/// Check if the parent expression is a call to `astimezone`. This assumes that
|
||||
/// the current expression is a `datetime.datetime` object.
|
||||
pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool {
|
||||
checker.semantic().current_expression_parent().is_some_and( |parent| {
|
||||
checker.semantic().expr_parent().is_some_and( |parent| {
|
||||
matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::{Binding, Imported};
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -56,13 +56,11 @@ pub(crate) fn unconventional_import_alias(
|
||||
binding: &Binding,
|
||||
conventions: &FxHashMap<String, String>,
|
||||
) -> Option<Diagnostic> {
|
||||
let Some(import) = binding.as_any_import() else {
|
||||
let Some(qualified_name) = binding.qualified_name() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let qualified_name = import.qualified_name();
|
||||
|
||||
let Some(expected_alias) = conventions.get(qualified_name.as_str()) else {
|
||||
let Some(expected_alias) = conventions.get(qualified_name) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -73,7 +71,7 @@ pub(crate) fn unconventional_import_alias(
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnconventionalImportAlias {
|
||||
name: qualified_name,
|
||||
name: qualified_name.to_string(),
|
||||
asname: expected_alias.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
|
||||
@@ -15,15 +15,6 @@ G004.py:5:27: G004 Logging statement uses f-string
|
||||
4 | logging.info(f"Hello {name}")
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^ G004
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
|
||||
G004.py:8:14: G004 Logging statement uses f-string
|
||||
|
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^ G004
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ pub(crate) fn any_eq_ne_annotation(checker: &mut Checker, name: &str, parameters
|
||||
return;
|
||||
};
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::{Decorator, Expr, Parameters, Ranged, TypeParam, TypeParams};
|
||||
use ruff_python_ast::{
|
||||
Decorator, Expr, ParameterWithDefault, Parameters, Ranged, TypeParam, TypeParams,
|
||||
};
|
||||
use ruff_python_semantic::analyze::visibility::{
|
||||
is_abstract, is_classmethod, is_new, is_overload, is_staticmethod,
|
||||
};
|
||||
@@ -75,23 +77,15 @@ pub(crate) fn custom_type_var_return_type(
|
||||
args: &Parameters,
|
||||
type_params: Option<&TypeParams>,
|
||||
) {
|
||||
// Given, e.g., `def foo(self: _S, arg: bytes) -> _T`, extract `_T`.
|
||||
let Some(return_annotation) = returns else {
|
||||
if args.args.is_empty() && args.posonlyargs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(returns) = returns else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Given, e.g., `def foo(self: _S, arg: bytes)`, extract `_S`.
|
||||
let Some(self_or_cls_annotation) = args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.next()
|
||||
.and_then(|parameter_with_default| parameter_with_default.parameter.annotation.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -103,12 +97,14 @@ pub(crate) fn custom_type_var_return_type(
|
||||
return;
|
||||
}
|
||||
|
||||
let returns = map_subscript(returns);
|
||||
|
||||
let uses_custom_var: bool =
|
||||
if is_classmethod(decorator_list, checker.semantic()) || is_new(name) {
|
||||
class_method(self_or_cls_annotation, return_annotation, type_params)
|
||||
class_method(args, returns, type_params)
|
||||
} else {
|
||||
// If not static, or a class method or __new__ we know it is an instance method
|
||||
instance_method(self_or_cls_annotation, return_annotation, type_params)
|
||||
instance_method(args, returns, type_params)
|
||||
};
|
||||
|
||||
if uses_custom_var {
|
||||
@@ -116,7 +112,7 @@ pub(crate) fn custom_type_var_return_type(
|
||||
CustomTypeVarReturnType {
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
return_annotation.range(),
|
||||
returns.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -124,11 +120,17 @@ pub(crate) fn custom_type_var_return_type(
|
||||
/// Returns `true` if the class method is annotated with a custom `TypeVar` that is likely
|
||||
/// private.
|
||||
fn class_method(
|
||||
cls_annotation: &Expr,
|
||||
args: &Parameters,
|
||||
return_annotation: &Expr,
|
||||
type_params: Option<&TypeParams>,
|
||||
) -> bool {
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, value, .. }) = cls_annotation else {
|
||||
let ParameterWithDefault { parameter, .. } = &args.args[0];
|
||||
|
||||
let Some(annotation) = ¶meter.annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, value, .. }) = annotation.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -146,7 +148,7 @@ fn class_method(
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(return_annotation) = map_subscript(return_annotation) else {
|
||||
let Expr::Name(return_annotation) = return_annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -160,20 +162,26 @@ fn class_method(
|
||||
/// Returns `true` if the instance method is annotated with a custom `TypeVar` that is likely
|
||||
/// private.
|
||||
fn instance_method(
|
||||
self_annotation: &Expr,
|
||||
args: &Parameters,
|
||||
return_annotation: &Expr,
|
||||
type_params: Option<&TypeParams>,
|
||||
) -> bool {
|
||||
let ParameterWithDefault { parameter, .. } = &args.args[0];
|
||||
|
||||
let Some(annotation) = ¶meter.annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: first_arg_type, ..
|
||||
}) = self_annotation
|
||||
}) = annotation.as_ref()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: return_type, ..
|
||||
}) = map_subscript(return_annotation)
|
||||
}) = return_annotation
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ pub(crate) fn non_self_return_type(
|
||||
parameters: &Parameters,
|
||||
async_: bool,
|
||||
) {
|
||||
let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
|
||||
let ScopeKind::Class(class_def) = checker.semantic().scope().kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -349,8 +349,8 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
fn is_special_assignment(target: &Expr, semantic: &SemanticModel) -> bool {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = target {
|
||||
match id.as_str() {
|
||||
"__all__" => semantic.current_scope().kind.is_module(),
|
||||
"__match_args__" | "__slots__" => semantic.current_scope().kind.is_class(),
|
||||
"__all__" => semantic.scope().kind.is_module(),
|
||||
"__match_args__" | "__slots__" => semantic.scope().kind.is_class(),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
@@ -569,9 +569,7 @@ pub(crate) fn unannotated_assignment_in_stub(
|
||||
return;
|
||||
}
|
||||
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
checker.semantic().current_scope().kind
|
||||
{
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = checker.semantic().scope().kind {
|
||||
if is_enum(arguments.as_deref(), checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker.semantic().current_scope().kind.is_class() {
|
||||
if !checker.semantic().scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,12 +96,11 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
|
||||
stmt.identifier(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let stmt = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().current_statement_parent();
|
||||
let stmt = checker.semantic().stmt();
|
||||
let parent = checker.semantic().stmt_parent();
|
||||
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
|
||||
diagnostic.set_fix(
|
||||
Fix::automatic(edit)
|
||||
.isolate(checker.isolation(checker.semantic().current_statement_parent())),
|
||||
Fix::automatic(edit).isolate(checker.isolation(checker.semantic().stmt_parent())),
|
||||
);
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -43,7 +43,7 @@ impl AlwaysAutofixableViolation for StringOrBytesTooLong {
|
||||
/// PYI053
|
||||
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) {
|
||||
// Ignore docstrings.
|
||||
if is_docstring_stmt(checker.semantic().current_statement()) {
|
||||
if is_docstring_stmt(checker.semantic().stmt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::Imported;
|
||||
use ruff_python_semantic::{Binding, BindingKind};
|
||||
use ruff_python_semantic::{Binding, BindingKind, FromImport};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -51,10 +50,10 @@ pub(crate) fn unaliased_collections_abc_set_import(
|
||||
checker: &Checker,
|
||||
binding: &Binding,
|
||||
) -> Option<Diagnostic> {
|
||||
let BindingKind::FromImport(import) = &binding.kind else {
|
||||
let BindingKind::FromImport(FromImport { qualified_name }) = &binding.kind else {
|
||||
return None;
|
||||
};
|
||||
if !matches!(import.call_path(), ["collections", "abc", "Set"]) {
|
||||
if qualified_name.as_str() != "collections.abc.Set" {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,7 @@ pub(crate) fn unused_private_type_var(
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) =
|
||||
checker.semantic().statement(source)
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -218,7 +217,7 @@ pub(crate) fn unused_private_protocol(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().statement(source) else {
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -261,7 +260,7 @@ pub(crate) fn unused_private_type_alias(
|
||||
};
|
||||
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
target, annotation, ..
|
||||
}) = checker.semantic().statement(source)
|
||||
}) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -305,7 +304,7 @@ pub(crate) fn unused_private_typed_dict(
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().statement(source) else {
|
||||
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,24 +21,17 @@ PYI019.py:14:54: PYI019 Methods like `bad_class_method` should return `typing.Se
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.py:35:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
17 | @classmethod
|
||||
18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
37 | # Python > 3.12
|
||||
38 | class PEP695BadDunderNew[T]:
|
||||
39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
33 | # Python > 3.12
|
||||
34 | class PEP695BadDunderNew[T]:
|
||||
35 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
PYI019.py:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.py:38:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
42 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
38 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
|
||||
@@ -21,24 +21,17 @@ PYI019.pyi:14:54: PYI019 Methods like `bad_class_method` should return `typing.S
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:18:55: PYI019 Methods like `bad_posonly_class_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.pyi:35:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
17 | @classmethod
|
||||
18 | def bad_posonly_class_method(cls: type[_S], /) -> _S: ... # PYI019
|
||||
| ^^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:39:63: PYI019 Methods like `__new__` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
37 | # Python > 3.12
|
||||
38 | class PEP695BadDunderNew[T]:
|
||||
39 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
33 | # Python > 3.12
|
||||
34 | class PEP695BadDunderNew[T]:
|
||||
35 | def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
PYI019.pyi:42:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
PYI019.pyi:38:46: PYI019 Methods like `generic_instance_method` should return `typing.Self` instead of a custom `TypeVar`
|
||||
|
|
||||
42 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
38 | def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
| ^ PYI019
|
||||
|
|
||||
|
||||
|
||||
@@ -246,8 +246,9 @@ pub(crate) fn unittest_assertion(
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// We're converting an expression to a statement, so avoid applying the fix if
|
||||
// the assertion is part of a larger expression.
|
||||
if checker.semantic().current_statement().is_expr_stmt()
|
||||
&& checker.semantic().current_expression_parent().is_none()
|
||||
if checker.semantic().stmt().is_expr_stmt()
|
||||
&& checker.semantic().expr_parent().is_none()
|
||||
&& !checker.semantic().scope().kind.is_lambda()
|
||||
&& !checker.indexer().comment_ranges().intersects(expr.range())
|
||||
{
|
||||
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {
|
||||
|
||||
@@ -550,7 +550,6 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &D
|
||||
arguments,
|
||||
edits::Parentheses::Preserve,
|
||||
checker.locator(),
|
||||
checker.source_type,
|
||||
)
|
||||
.map(Fix::suggested)
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arguments, Constant, Decorator, Expr, ExprContext, PySourceType, Ranged,
|
||||
};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Decorator, Expr, ExprContext, Ranged};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
@@ -97,12 +95,7 @@ fn elts_to_csv(elts: &[Expr], generator: Generator) -> Option<String> {
|
||||
/// ```
|
||||
///
|
||||
/// This method assumes that the first argument is a string.
|
||||
fn get_parametrize_name_range(
|
||||
decorator: &Decorator,
|
||||
expr: &Expr,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
) -> TextRange {
|
||||
fn get_parametrize_name_range(decorator: &Decorator, expr: &Expr, locator: &Locator) -> TextRange {
|
||||
let mut locations = Vec::new();
|
||||
let mut implicit_concat = None;
|
||||
|
||||
@@ -110,7 +103,7 @@ fn get_parametrize_name_range(
|
||||
// decorator to find them.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(decorator.range()),
|
||||
source_type.as_mode(),
|
||||
Mode::Module,
|
||||
decorator.start(),
|
||||
)
|
||||
.flatten()
|
||||
@@ -148,12 +141,8 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
if names.len() > 1 {
|
||||
match names_type {
|
||||
types::ParametrizeNameType::Tuple => {
|
||||
let name_range = get_parametrize_name_range(
|
||||
decorator,
|
||||
expr,
|
||||
checker.locator(),
|
||||
checker.source_type,
|
||||
);
|
||||
let name_range =
|
||||
get_parametrize_name_range(decorator, expr, checker.locator());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
PytestParametrizeNamesWrongType {
|
||||
expected: names_type,
|
||||
@@ -183,12 +172,8 @@ fn check_names(checker: &mut Checker, decorator: &Decorator, expr: &Expr) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
types::ParametrizeNameType::List => {
|
||||
let name_range = get_parametrize_name_range(
|
||||
decorator,
|
||||
expr,
|
||||
checker.locator(),
|
||||
checker.source_type,
|
||||
);
|
||||
let name_range =
|
||||
get_parametrize_name_range(decorator, expr, checker.locator());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
PytestParametrizeNamesWrongType {
|
||||
expected: names_type,
|
||||
|
||||
@@ -11,35 +11,6 @@ use crate::registry::Rule;
|
||||
|
||||
use super::helpers::is_empty_or_null_string;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest.raises` context managers with multiple statements.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When a `pytest.raises` is used as a context manager and contains multiple
|
||||
/// statements, it can lead to the test passing when it actually should fail.
|
||||
/// To avoid this, a `pytest.raises` context manager should only contain
|
||||
/// a single simple statement that raises the expected exception.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(MyError):
|
||||
/// setup() # may raise `MyError`
|
||||
/// func_to_test()
|
||||
/// assert foo() # not executed
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// setup()
|
||||
/// with pytest.raises(MyException):
|
||||
/// func_to_test()
|
||||
/// assert foo()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||
#[violation]
|
||||
pub struct PytestRaisesWithMultipleStatements;
|
||||
|
||||
@@ -50,41 +21,6 @@ impl Violation for PytestRaisesWithMultipleStatements {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest.raises` calls without a `match` parameter.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `pytest.raises(Error)` will catch any `Error` and may catch errors that are
|
||||
/// unrelated to the code under test. To avoid this, `pytest.raises` should be
|
||||
/// called with a `match` parameter. The exception names that require a `match`
|
||||
/// parameter can be configured via the
|
||||
/// `flake8-pytest-style.raises-require-match-for` and
|
||||
/// `flake8-pytest-style.raises-extend-require-match-for` settings.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(ValueError):
|
||||
/// ...
|
||||
///
|
||||
/// # empty string is also an error
|
||||
/// with pytest.raises(ValueError, match=""):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(ValueError, match="expected message"):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-pytest-style.raises-require-match-for`
|
||||
/// - `flake8-pytest-style.raises-extend-require-match-for`
|
||||
///
|
||||
/// ## References
|
||||
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||
#[violation]
|
||||
pub struct PytestRaisesTooBroad {
|
||||
exception: String,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, PySourceType, Ranged};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Ranged};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||
return;
|
||||
}
|
||||
|
||||
let range = match_parens(func.end(), checker.locator(), checker.source_type)
|
||||
let range = match_parens(func.end(), checker.locator())
|
||||
.expect("Expected call to include parentheses");
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
@@ -82,18 +82,14 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||
}
|
||||
|
||||
/// Return the range of the first parenthesis pair after a given [`TextSize`].
|
||||
fn match_parens(
|
||||
start: TextSize,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
) -> Option<TextRange> {
|
||||
fn match_parens(start: TextSize, locator: &Locator) -> Option<TextRange> {
|
||||
let contents = &locator.contents()[usize::from(start)..];
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
let mut count = 0u32;
|
||||
|
||||
for (tok, range) in lexer::lex_starts_at(contents, source_type.as_mode(), start).flatten() {
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, start).flatten() {
|
||||
match tok {
|
||||
Tok::Lpar => {
|
||||
if count == 0 {
|
||||
|
||||
@@ -77,7 +77,7 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
|
||||
|
||||
// Ignore accesses on instances within special methods (e.g., `__eq__`).
|
||||
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
|
||||
checker.semantic().current_scope().kind
|
||||
checker.semantic().scope().kind
|
||||
{
|
||||
if matches!(
|
||||
name.as_str(),
|
||||
|
||||
@@ -378,7 +378,6 @@ pub(crate) fn nested_if_statements(
|
||||
let colon = first_colon_range(
|
||||
TextRange::new(test.end(), first_stmt.start()),
|
||||
checker.locator().contents(),
|
||||
checker.source_type.is_jupyter(),
|
||||
);
|
||||
|
||||
// Check if the parent is already emitting a larger diagnostic including this if statement
|
||||
|
||||
@@ -155,14 +155,14 @@ pub(crate) fn negation_with_equal_op(
|
||||
if !matches!(&ops[..], [CmpOp::Eq]) {
|
||||
return;
|
||||
}
|
||||
if is_exception_check(checker.semantic().current_statement()) {
|
||||
if is_exception_check(checker.semantic().stmt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid flagging issues in dunder implementations.
|
||||
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. })
|
||||
| ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) =
|
||||
&checker.semantic().current_scope().kind
|
||||
&checker.semantic().scope().kind
|
||||
{
|
||||
if is_dunder_method(name) {
|
||||
return;
|
||||
@@ -213,14 +213,14 @@ pub(crate) fn negation_with_not_equal_op(
|
||||
if !matches!(&ops[..], [CmpOp::NotEq]) {
|
||||
return;
|
||||
}
|
||||
if is_exception_check(checker.semantic().current_statement()) {
|
||||
if is_exception_check(checker.semantic().stmt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid flagging issues in dunder implementations.
|
||||
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. })
|
||||
| ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) =
|
||||
&checker.semantic().current_scope().kind
|
||||
&checker.semantic().scope().kind
|
||||
{
|
||||
if is_dunder_method(name) {
|
||||
return;
|
||||
|
||||
@@ -119,7 +119,6 @@ pub(crate) fn multiple_with_statements(
|
||||
body.first().expect("Expected body to be non-empty").start(),
|
||||
),
|
||||
checker.locator().contents(),
|
||||
checker.source_type.is_jupyter(),
|
||||
);
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
|
||||
@@ -43,7 +43,7 @@ impl Violation for OpenFileWithContextHandler {
|
||||
/// Return `true` if the current expression is nested in an `await
|
||||
/// exit_stack.enter_async_context` call.
|
||||
fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
let Some(expr) = semantic.current_expression_grandparent() else {
|
||||
let Some(expr) = semantic.expr_grandparent() else {
|
||||
return false;
|
||||
};
|
||||
let Expr::Await(ast::ExprAwait { value, range: _ }) = expr else {
|
||||
@@ -58,7 +58,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
if attr != "enter_async_context" {
|
||||
return false;
|
||||
}
|
||||
for parent in semantic.current_statements() {
|
||||
for parent in semantic.parents() {
|
||||
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
||||
for item in items {
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
||||
@@ -77,7 +77,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
/// Return `true` if the current expression is nested in an
|
||||
/// `exit_stack.enter_context` call.
|
||||
fn match_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
let Some(expr) = semantic.current_expression_parent() else {
|
||||
let Some(expr) = semantic.expr_parent() else {
|
||||
return false;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||
@@ -89,7 +89,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
if attr != "enter_context" {
|
||||
return false;
|
||||
}
|
||||
for parent in semantic.current_statements() {
|
||||
for parent in semantic.parents() {
|
||||
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
|
||||
for item in items {
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
|
||||
}
|
||||
|
||||
// Ex) `with open("foo.txt") as f: ...`
|
||||
if checker.semantic().current_statement().is_with_stmt() {
|
||||
if checker.semantic().stmt().is_with_stmt() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Violation for ReimplementedBuiltin {
|
||||
|
||||
/// SIM110, SIM111
|
||||
pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
|
||||
if !checker.semantic().current_scope().kind.is_any_function() {
|
||||
if !checker.semantic().scope().kind.is_any_function() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
|
||||
// - `for` loop with an `else: return True` or `else: return False`.
|
||||
// - `for` loop followed by `return True` or `return False`.
|
||||
let Some(terminal) = match_else_return(stmt).or_else(|| {
|
||||
let parent = checker.semantic().current_statement_parent()?;
|
||||
let parent = checker.semantic().stmt_parent()?;
|
||||
let suite = traversal::suite(stmt, parent)?;
|
||||
let sibling = traversal::next_sibling(stmt, suite)?;
|
||||
match_sibling_return(stmt, sibling)
|
||||
|
||||
@@ -35,7 +35,7 @@ pub(crate) fn runtime_evaluated(
|
||||
}
|
||||
|
||||
fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticModel) -> bool {
|
||||
let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
|
||||
let ScopeKind::Class(class_def) = &semantic.scope().kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticMode
|
||||
}
|
||||
|
||||
fn runtime_evaluated_decorators(decorators: &[String], semantic: &SemanticModel) -> bool {
|
||||
let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
|
||||
let ScopeKind::Class(class_def) = &semantic.scope().kind else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ pub(crate) fn empty_type_checking_block(checker: &mut Checker, stmt: &ast::StmtI
|
||||
let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// Delete the entire type-checking block.
|
||||
let stmt = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().current_statement_parent();
|
||||
let stmt = checker.semantic().stmt();
|
||||
let parent = checker.semantic().stmt_parent();
|
||||
let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
|
||||
diagnostic.set_fix(Fix::automatic(edit).isolate(checker.isolation(parent)));
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::{AnyImport, Imported, ResolvedReferenceId, Scope, StatementId};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_python_semantic::{NodeId, ResolvedReferenceId, Scope};
|
||||
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::importer::ImportedMembers;
|
||||
use crate::importer::StmtImports;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for runtime imports defined in a type-checking block.
|
||||
@@ -71,13 +69,13 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
// Collect all runtime imports by statement.
|
||||
let mut errors_by_statement: FxHashMap<StatementId, Vec<ImportBinding>> = FxHashMap::default();
|
||||
let mut ignores_by_statement: FxHashMap<StatementId, Vec<ImportBinding>> = FxHashMap::default();
|
||||
let mut errors_by_statement: FxHashMap<NodeId, Vec<Import>> = FxHashMap::default();
|
||||
let mut ignores_by_statement: FxHashMap<NodeId, Vec<Import>> = FxHashMap::default();
|
||||
|
||||
for binding_id in scope.binding_ids() {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
|
||||
let Some(import) = binding.as_any_import() else {
|
||||
let Some(qualified_name) = binding.qualified_name() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -94,12 +92,12 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
.is_runtime()
|
||||
})
|
||||
{
|
||||
let Some(statement_id) = binding.source else {
|
||||
let Some(stmt_id) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let import = ImportBinding {
|
||||
import,
|
||||
let import = Import {
|
||||
qualified_name,
|
||||
reference_id,
|
||||
range: binding.range,
|
||||
parent_range: binding.parent_range(checker.semantic()),
|
||||
@@ -114,29 +112,26 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
})
|
||||
{
|
||||
ignores_by_statement
|
||||
.entry(statement_id)
|
||||
.entry(stmt_id)
|
||||
.or_default()
|
||||
.push(import);
|
||||
} else {
|
||||
errors_by_statement
|
||||
.entry(statement_id)
|
||||
.or_default()
|
||||
.push(import);
|
||||
errors_by_statement.entry(stmt_id).or_default().push(import);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a diagnostic for every import, but share a fix across all imports within the same
|
||||
// statement (excluding those that are ignored).
|
||||
for (statement_id, imports) in errors_by_statement {
|
||||
for (stmt_id, imports) in errors_by_statement {
|
||||
let fix = if checker.patch(Rule::RuntimeImportInTypeCheckingBlock) {
|
||||
fix_imports(checker, statement_id, &imports).ok()
|
||||
fix_imports(checker, stmt_id, &imports).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for ImportBinding {
|
||||
import,
|
||||
for Import {
|
||||
qualified_name,
|
||||
range,
|
||||
parent_range,
|
||||
..
|
||||
@@ -144,7 +139,7 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RuntimeImportInTypeCheckingBlock {
|
||||
qualified_name: import.qualified_name(),
|
||||
qualified_name: qualified_name.to_string(),
|
||||
},
|
||||
range,
|
||||
);
|
||||
@@ -160,8 +155,8 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
|
||||
// Separately, generate a diagnostic for every _ignored_ import, to ensure that the
|
||||
// suppression comments aren't marked as unused.
|
||||
for ImportBinding {
|
||||
import,
|
||||
for Import {
|
||||
qualified_name,
|
||||
range,
|
||||
parent_range,
|
||||
..
|
||||
@@ -169,7 +164,7 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
RuntimeImportInTypeCheckingBlock {
|
||||
qualified_name: import.qualified_name(),
|
||||
qualified_name: qualified_name.to_string(),
|
||||
},
|
||||
range,
|
||||
);
|
||||
@@ -181,9 +176,9 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||
}
|
||||
|
||||
/// A runtime-required import with its surrounding context.
|
||||
struct ImportBinding<'a> {
|
||||
struct Import<'a> {
|
||||
/// The qualified name of the import (e.g., `typing.List` for `from typing import List`).
|
||||
import: AnyImport<'a>,
|
||||
qualified_name: &'a str,
|
||||
/// The first reference to the imported symbol.
|
||||
reference_id: ResolvedReferenceId,
|
||||
/// The trimmed range of the import (e.g., `List` in `from typing import List`).
|
||||
@@ -193,24 +188,18 @@ struct ImportBinding<'a> {
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove runtime imports from a type-checking block.
|
||||
fn fix_imports(
|
||||
checker: &Checker,
|
||||
statement_id: StatementId,
|
||||
imports: &[ImportBinding],
|
||||
) -> Result<Fix> {
|
||||
let statement = checker.semantic().statement(statement_id);
|
||||
let parent = checker.semantic().parent_statement(statement_id);
|
||||
|
||||
let member_names: Vec<Cow<'_, str>> = imports
|
||||
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[Import]) -> Result<Fix> {
|
||||
let stmt = checker.semantic().stmts[stmt_id];
|
||||
let parent = checker.semantic().stmts.parent(stmt);
|
||||
let qualified_names: Vec<&str> = imports
|
||||
.iter()
|
||||
.map(|ImportBinding { import, .. }| import)
|
||||
.map(Imported::member_name)
|
||||
.map(|Import { qualified_name, .. }| *qualified_name)
|
||||
.collect();
|
||||
|
||||
// Find the first reference across all imports.
|
||||
let at = imports
|
||||
.iter()
|
||||
.map(|ImportBinding { reference_id, .. }| {
|
||||
.map(|Import { reference_id, .. }| {
|
||||
checker.semantic().reference(*reference_id).range().start()
|
||||
})
|
||||
.min()
|
||||
@@ -218,8 +207,8 @@ fn fix_imports(
|
||||
|
||||
// Step 1) Remove the import.
|
||||
let remove_import_edit = autofix::edits::remove_unused_imports(
|
||||
member_names.iter().map(AsRef::as_ref),
|
||||
statement,
|
||||
qualified_names.iter().copied(),
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
@@ -228,9 +217,9 @@ fn fix_imports(
|
||||
|
||||
// Step 2) Add the import to the top-level.
|
||||
let add_import_edit = checker.importer().runtime_import_edit(
|
||||
&ImportedMembers {
|
||||
statement,
|
||||
names: member_names.iter().map(AsRef::as_ref).collect(),
|
||||
&StmtImports {
|
||||
stmt,
|
||||
qualified_names,
|
||||
},
|
||||
at,
|
||||
)?;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Result;
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, DiagnosticKind, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_semantic::{AnyImport, Binding, Imported, ResolvedReferenceId, Scope, StatementId};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_python_semantic::{Binding, NodeId, ResolvedReferenceId, Scope};
|
||||
|
||||
use crate::autofix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::importer::ImportedMembers;
|
||||
use crate::importer::StmtImports;
|
||||
use crate::rules::isort::{categorize, ImportSection, ImportType};
|
||||
|
||||
/// ## What it does
|
||||
@@ -190,9 +188,9 @@ pub(crate) fn typing_only_runtime_import(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
// Collect all typing-only imports by statement and import type.
|
||||
let mut errors_by_statement: FxHashMap<(StatementId, ImportType), Vec<ImportBinding>> =
|
||||
let mut errors_by_statement: FxHashMap<(NodeId, ImportType), Vec<Import>> =
|
||||
FxHashMap::default();
|
||||
let mut ignores_by_statement: FxHashMap<(StatementId, ImportType), Vec<ImportBinding>> =
|
||||
let mut ignores_by_statement: FxHashMap<(NodeId, ImportType), Vec<Import>> =
|
||||
FxHashMap::default();
|
||||
|
||||
for binding_id in scope.binding_ids() {
|
||||
@@ -208,10 +206,23 @@ pub(crate) fn typing_only_runtime_import(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(import) = binding.as_any_import() else {
|
||||
let Some(qualified_name) = binding.qualified_name() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_exempt(
|
||||
qualified_name,
|
||||
&checker
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.exempt_modules
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>(),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(reference_id) = binding.references.first().copied() else {
|
||||
continue;
|
||||
};
|
||||
@@ -225,25 +236,20 @@ pub(crate) fn typing_only_runtime_import(
|
||||
.is_typing()
|
||||
})
|
||||
{
|
||||
let qualified_name = import.qualified_name();
|
||||
|
||||
if is_exempt(
|
||||
qualified_name.as_str(),
|
||||
&checker
|
||||
.settings
|
||||
.flake8_type_checking
|
||||
.exempt_modules
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>(),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Extract the module base and level from the full name.
|
||||
// Ex) `foo.bar.baz` -> `foo`, `0`
|
||||
// Ex) `.foo.bar.baz` -> `foo`, `1`
|
||||
let level = qualified_name
|
||||
.chars()
|
||||
.take_while(|c| *c == '.')
|
||||
.count()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
// Categorize the import, using coarse-grained categorization.
|
||||
let import_type = match categorize(
|
||||
qualified_name.as_str(),
|
||||
None,
|
||||
qualified_name,
|
||||
Some(level),
|
||||
&checker.settings.src,
|
||||
checker.package(),
|
||||
&checker.settings.isort.known_modules,
|
||||
@@ -265,12 +271,12 @@ pub(crate) fn typing_only_runtime_import(
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(statement_id) = binding.source else {
|
||||
let Some(stmt_id) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let import = ImportBinding {
|
||||
import,
|
||||
let import = Import {
|
||||
qualified_name,
|
||||
reference_id,
|
||||
range: binding.range,
|
||||
parent_range: binding.parent_range(checker.semantic()),
|
||||
@@ -282,12 +288,12 @@ pub(crate) fn typing_only_runtime_import(
|
||||
})
|
||||
{
|
||||
ignores_by_statement
|
||||
.entry((statement_id, import_type))
|
||||
.entry((stmt_id, import_type))
|
||||
.or_default()
|
||||
.push(import);
|
||||
} else {
|
||||
errors_by_statement
|
||||
.entry((statement_id, import_type))
|
||||
.entry((stmt_id, import_type))
|
||||
.or_default()
|
||||
.push(import);
|
||||
}
|
||||
@@ -296,22 +302,24 @@ pub(crate) fn typing_only_runtime_import(
|
||||
|
||||
// Generate a diagnostic for every import, but share a fix across all imports within the same
|
||||
// statement (excluding those that are ignored).
|
||||
for ((statement_id, import_type), imports) in errors_by_statement {
|
||||
for ((stmt_id, import_type), imports) in errors_by_statement {
|
||||
let fix = if checker.patch(rule_for(import_type)) {
|
||||
fix_imports(checker, statement_id, &imports).ok()
|
||||
fix_imports(checker, stmt_id, &imports).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for ImportBinding {
|
||||
import,
|
||||
for Import {
|
||||
qualified_name,
|
||||
range,
|
||||
parent_range,
|
||||
..
|
||||
} in imports
|
||||
{
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(diagnostic_for(import_type, import.qualified_name()), range);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
diagnostic_for(import_type, qualified_name.to_string()),
|
||||
range,
|
||||
);
|
||||
if let Some(range) = parent_range {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
@@ -325,15 +333,17 @@ pub(crate) fn typing_only_runtime_import(
|
||||
// Separately, generate a diagnostic for every _ignored_ import, to ensure that the
|
||||
// suppression comments aren't marked as unused.
|
||||
for ((_, import_type), imports) in ignores_by_statement {
|
||||
for ImportBinding {
|
||||
import,
|
||||
for Import {
|
||||
qualified_name,
|
||||
range,
|
||||
parent_range,
|
||||
..
|
||||
} in imports
|
||||
{
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(diagnostic_for(import_type, import.qualified_name()), range);
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
diagnostic_for(import_type, qualified_name.to_string()),
|
||||
range,
|
||||
);
|
||||
if let Some(range) = parent_range {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
@@ -343,9 +353,9 @@ pub(crate) fn typing_only_runtime_import(
|
||||
}
|
||||
|
||||
/// A runtime-required import with its surrounding context.
|
||||
struct ImportBinding<'a> {
|
||||
struct Import<'a> {
|
||||
/// The qualified name of the import (e.g., `typing.List` for `from typing import List`).
|
||||
import: AnyImport<'a>,
|
||||
qualified_name: &'a str,
|
||||
/// The first reference to the imported symbol.
|
||||
reference_id: ResolvedReferenceId,
|
||||
/// The trimmed range of the import (e.g., `List` in `from typing import List`).
|
||||
@@ -376,13 +386,13 @@ fn diagnostic_for(import_type: ImportType, qualified_name: String) -> Diagnostic
|
||||
|
||||
/// Return `true` if `this` is implicitly loaded via importing `that`.
|
||||
fn is_implicit_import(this: &Binding, that: &Binding) -> bool {
|
||||
let Some(this_import) = this.as_any_import() else {
|
||||
let Some(this_module) = this.module_name() else {
|
||||
return false;
|
||||
};
|
||||
let Some(that_import) = that.as_any_import() else {
|
||||
let Some(that_module) = that.module_name() else {
|
||||
return false;
|
||||
};
|
||||
this_import.module_name() == that_import.module_name()
|
||||
this_module == that_module
|
||||
}
|
||||
|
||||
/// Return `true` if `name` is exempt from typing-only enforcement.
|
||||
@@ -402,24 +412,18 @@ fn is_exempt(name: &str, exempt_modules: &[&str]) -> bool {
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove typing-only imports from a runtime context.
|
||||
fn fix_imports(
|
||||
checker: &Checker,
|
||||
statement_id: StatementId,
|
||||
imports: &[ImportBinding],
|
||||
) -> Result<Fix> {
|
||||
let statement = checker.semantic().statement(statement_id);
|
||||
let parent = checker.semantic().parent_statement(statement_id);
|
||||
|
||||
let member_names: Vec<Cow<'_, str>> = imports
|
||||
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[Import]) -> Result<Fix> {
|
||||
let stmt = checker.semantic().stmts[stmt_id];
|
||||
let parent = checker.semantic().stmts.parent(stmt);
|
||||
let qualified_names: Vec<&str> = imports
|
||||
.iter()
|
||||
.map(|ImportBinding { import, .. }| import)
|
||||
.map(Imported::member_name)
|
||||
.map(|Import { qualified_name, .. }| *qualified_name)
|
||||
.collect();
|
||||
|
||||
// Find the first reference across all imports.
|
||||
let at = imports
|
||||
.iter()
|
||||
.map(|ImportBinding { reference_id, .. }| {
|
||||
.map(|Import { reference_id, .. }| {
|
||||
checker.semantic().reference(*reference_id).range().start()
|
||||
})
|
||||
.min()
|
||||
@@ -427,8 +431,8 @@ fn fix_imports(
|
||||
|
||||
// Step 1) Remove the import.
|
||||
let remove_import_edit = autofix::edits::remove_unused_imports(
|
||||
member_names.iter().map(AsRef::as_ref),
|
||||
statement,
|
||||
qualified_names.iter().copied(),
|
||||
stmt,
|
||||
parent,
|
||||
checker.locator(),
|
||||
checker.stylist(),
|
||||
@@ -437,13 +441,12 @@ fn fix_imports(
|
||||
|
||||
// Step 2) Add the import to a `TYPE_CHECKING` block.
|
||||
let add_import_edit = checker.importer().typing_import_edit(
|
||||
&ImportedMembers {
|
||||
statement,
|
||||
names: member_names.iter().map(AsRef::as_ref).collect(),
|
||||
&StmtImports {
|
||||
stmt,
|
||||
qualified_names,
|
||||
},
|
||||
at,
|
||||
checker.semantic(),
|
||||
checker.source_type,
|
||||
)?;
|
||||
|
||||
Ok(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, PySourceType, Ranged, Stmt};
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_source_file::Locator;
|
||||
@@ -13,7 +13,6 @@ pub(crate) fn annotate_imports<'a>(
|
||||
comments: Vec<Comment<'a>>,
|
||||
locator: &Locator,
|
||||
split_on_trailing_comma: bool,
|
||||
source_type: PySourceType,
|
||||
) -> Vec<AnnotatedImport<'a>> {
|
||||
let mut comments_iter = comments.into_iter().peekable();
|
||||
|
||||
@@ -120,7 +119,7 @@ pub(crate) fn annotate_imports<'a>(
|
||||
names: aliases,
|
||||
level: level.map(|level| level.to_u32()),
|
||||
trailing_comma: if split_on_trailing_comma {
|
||||
trailing_comma(import, locator, source_type)
|
||||
trailing_comma(import, locator)
|
||||
} else {
|
||||
TrailingComma::default()
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_source_file::Locator;
|
||||
@@ -23,13 +22,9 @@ impl Comment<'_> {
|
||||
}
|
||||
|
||||
/// Collect all comments in an import block.
|
||||
pub(crate) fn collect_comments<'a>(
|
||||
range: TextRange,
|
||||
locator: &'a Locator,
|
||||
source_type: PySourceType,
|
||||
) -> Vec<Comment<'a>> {
|
||||
pub(crate) fn collect_comments<'a>(range: TextRange, locator: &'a Locator) -> Vec<Comment<'a>> {
|
||||
let contents = locator.slice(range);
|
||||
lexer::lex_starts_at(contents, source_type.as_mode(), range.start())
|
||||
lexer::lex_starts_at(contents, Mode::Module, range.start())
|
||||
.flatten()
|
||||
.filter_map(|(tok, range)| {
|
||||
if let Tok::Comment(value) = tok {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode, Tok};
|
||||
|
||||
use ruff_python_trivia::PythonWhitespace;
|
||||
use ruff_source_file::{Locator, UniversalNewlines};
|
||||
@@ -8,15 +8,11 @@ use crate::rules::isort::types::TrailingComma;
|
||||
|
||||
/// Return `true` if a `Stmt::ImportFrom` statement ends with a magic
|
||||
/// trailing comma.
|
||||
pub(super) fn trailing_comma(
|
||||
stmt: &Stmt,
|
||||
locator: &Locator,
|
||||
source_type: PySourceType,
|
||||
) -> TrailingComma {
|
||||
pub(super) fn trailing_comma(stmt: &Stmt, locator: &Locator) -> TrailingComma {
|
||||
let contents = locator.slice(stmt.range());
|
||||
let mut count = 0u32;
|
||||
let mut trailing_comma = TrailingComma::Absent;
|
||||
for (tok, _) in lexer::lex_starts_at(contents, source_type.as_mode(), stmt.start()).flatten() {
|
||||
for (tok, _) in lexer::lex_starts_at(contents, Mode::Module, stmt.start()).flatten() {
|
||||
if matches!(tok, Tok::Lpar) {
|
||||
count = count.saturating_add(1);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ pub use categorize::{ImportSection, ImportType};
|
||||
use comments::Comment;
|
||||
use normalize::normalize_imports;
|
||||
use order::order_imports;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_source_file::Locator;
|
||||
use settings::RelativeImportsOrder;
|
||||
@@ -73,7 +72,6 @@ pub(crate) fn format_imports(
|
||||
stylist: &Stylist,
|
||||
src: &[PathBuf],
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
combine_as_imports: bool,
|
||||
force_single_line: bool,
|
||||
force_sort_within_sections: bool,
|
||||
@@ -96,13 +94,7 @@ pub(crate) fn format_imports(
|
||||
section_order: &[ImportSection],
|
||||
) -> String {
|
||||
let trailer = &block.trailer;
|
||||
let block = annotate_imports(
|
||||
&block.imports,
|
||||
comments,
|
||||
locator,
|
||||
split_on_trailing_comma,
|
||||
source_type,
|
||||
);
|
||||
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
|
||||
|
||||
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
|
||||
let block = normalize_imports(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use log::error;
|
||||
use ruff_python_ast::{self as ast, PySourceType, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Stmt, Suite};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
@@ -91,7 +91,7 @@ fn add_required_import(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
settings: &Settings,
|
||||
source_type: PySourceType,
|
||||
is_stub: bool,
|
||||
) -> Option<Diagnostic> {
|
||||
// Don't add imports to semantically-empty files.
|
||||
if python_ast.iter().all(is_docstring_stmt) {
|
||||
@@ -99,7 +99,7 @@ fn add_required_import(
|
||||
}
|
||||
|
||||
// We don't need to add `__future__` imports to stubs.
|
||||
if source_type.is_stub() && required_import.is_future_import() {
|
||||
if is_stub && required_import.is_future_import() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ pub(crate) fn add_required_imports(
|
||||
locator: &Locator,
|
||||
stylist: &Stylist,
|
||||
settings: &Settings,
|
||||
source_type: PySourceType,
|
||||
is_stub: bool,
|
||||
) -> Vec<Diagnostic> {
|
||||
settings
|
||||
.isort
|
||||
@@ -172,7 +172,7 @@ pub(crate) fn add_required_imports(
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
is_stub,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn add_required_imports(
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
is_stub,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
||||
use ruff_python_ast::{Ranged, Stmt};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
@@ -87,7 +87,6 @@ pub(crate) fn organize_imports(
|
||||
indexer: &Indexer,
|
||||
settings: &Settings,
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
) -> Option<Diagnostic> {
|
||||
let indentation = locator.slice(extract_indentation_range(&block.imports, locator));
|
||||
let indentation = leading_indentation(indentation);
|
||||
@@ -106,7 +105,6 @@ pub(crate) fn organize_imports(
|
||||
let comments = comments::collect_comments(
|
||||
TextRange::new(range.start(), locator.full_line_end(range.end())),
|
||||
locator,
|
||||
source_type,
|
||||
);
|
||||
|
||||
let trailing_line_end = if block.trailer.is_none() {
|
||||
@@ -125,7 +123,6 @@ pub(crate) fn organize_imports(
|
||||
stylist,
|
||||
&settings.src,
|
||||
package,
|
||||
source_type,
|
||||
settings.isort.combine_as_imports,
|
||||
settings.isort.force_single_line,
|
||||
settings.isort.force_sort_within_sections,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::{BindingKind, Imported, SemanticModel};
|
||||
|
||||
#[derive(Debug)]
|
||||
use ruff_python_semantic::{BindingKind, Import, SemanticModel};
|
||||
|
||||
pub(super) enum Resolution {
|
||||
/// The expression resolves to an irrelevant expression type (e.g., a constant).
|
||||
IrrelevantExpression,
|
||||
@@ -26,23 +26,26 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti
|
||||
| Expr::ListComp(_)
|
||||
| Expr::DictComp(_)
|
||||
| Expr::GeneratorExp(_) => Resolution::IrrelevantExpression,
|
||||
Expr::Name(ast::ExprName { id, .. }) => semantic.find_binding(id).map_or(
|
||||
Resolution::IrrelevantBinding,
|
||||
|binding| match &binding.kind {
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::UnpackedAssignment
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::Global
|
||||
| BindingKind::Nonlocal(_) => Resolution::RelevantLocal,
|
||||
BindingKind::Import(import) if matches!(import.call_path(), ["pandas"]) => {
|
||||
Resolution::PandasModule
|
||||
}
|
||||
_ => Resolution::IrrelevantBinding,
|
||||
},
|
||||
),
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
semantic
|
||||
.find_binding(id)
|
||||
.map_or(Resolution::IrrelevantBinding, |binding| {
|
||||
match binding.kind {
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::UnpackedAssignment
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::Global
|
||||
| BindingKind::Nonlocal(_) => Resolution::RelevantLocal,
|
||||
BindingKind::Import(Import {
|
||||
qualified_name: module,
|
||||
}) if module == "pandas" => Resolution::PandasModule,
|
||||
_ => Resolution::IrrelevantBinding,
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => Resolution::RelevantLocal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(crate) fn attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &
|
||||
};
|
||||
|
||||
// Avoid flagging on function calls (e.g., `df.values()`).
|
||||
if let Some(parent) = checker.semantic().current_expression_parent() {
|
||||
if let Some(parent) = checker.semantic().expr_parent() {
|
||||
if matches!(parent, Expr::Call(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::{self as ast, Keyword, PySourceType, Ranged};
|
||||
use ruff_python_semantic::BindingKind;
|
||||
use ruff_python_semantic::Imported;
|
||||
use ruff_python_ast::{self as ast, Keyword, Ranged};
|
||||
use ruff_python_semantic::{BindingKind, Import};
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::autofix::edits::{remove_argument, Parentheses};
|
||||
@@ -58,11 +57,12 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
.first()
|
||||
.and_then(|module| checker.semantic().find_binding(module))
|
||||
.is_some_and(|binding| {
|
||||
if let BindingKind::Import(import) = &binding.kind {
|
||||
matches!(import.call_path(), ["pandas"])
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(
|
||||
binding.kind,
|
||||
BindingKind::Import(Import {
|
||||
qualified_name: "pandas"
|
||||
})
|
||||
)
|
||||
})
|
||||
{
|
||||
return;
|
||||
@@ -84,16 +84,18 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
// the star argument _doesn't_ contain an override).
|
||||
// 2. The call is part of a larger expression (we're converting an expression to a
|
||||
// statement, and expressions can't contain statements).
|
||||
// 3. The call is in a lambda (we can't assign to a variable in a lambda). This
|
||||
// should be unnecessary, as lambdas are expressions, and so (2) should apply,
|
||||
// but we don't currently restore expression stacks when parsing deferred nodes,
|
||||
// and so the parent is lost.
|
||||
if !seen_star
|
||||
&& checker.semantic().current_statement().is_expr_stmt()
|
||||
&& checker.semantic().current_expression_parent().is_none()
|
||||
&& checker.semantic().stmt().is_expr_stmt()
|
||||
&& checker.semantic().expr_parent().is_none()
|
||||
&& !checker.semantic().scope().kind.is_lambda()
|
||||
{
|
||||
if let Some(fix) = convert_inplace_argument_to_assignment(
|
||||
call,
|
||||
keyword,
|
||||
checker.source_type,
|
||||
checker.locator(),
|
||||
) {
|
||||
if let Some(fix) =
|
||||
convert_inplace_argument_to_assignment(checker.locator(), call, keyword)
|
||||
{
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
}
|
||||
@@ -111,10 +113,9 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
/// Remove the `inplace` argument from a function call and replace it with an
|
||||
/// assignment.
|
||||
fn convert_inplace_argument_to_assignment(
|
||||
locator: &Locator,
|
||||
call: &ast::ExprCall,
|
||||
keyword: &Keyword,
|
||||
source_type: PySourceType,
|
||||
locator: &Locator,
|
||||
) -> Option<Fix> {
|
||||
// Add the assignment.
|
||||
let attr = call.func.as_attribute_expr()?;
|
||||
@@ -124,14 +125,8 @@ fn convert_inplace_argument_to_assignment(
|
||||
);
|
||||
|
||||
// Remove the `inplace` argument.
|
||||
let remove_argument = remove_argument(
|
||||
keyword,
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
locator,
|
||||
source_type,
|
||||
)
|
||||
.ok()?;
|
||||
let remove_argument =
|
||||
remove_argument(keyword, &call.arguments, Parentheses::Preserve, locator).ok()?;
|
||||
|
||||
Some(Fix::suggested_edits(insert_assignment, [remove_argument]))
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(crate) fn mixed_case_variable_in_class_scope(
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().stmt();
|
||||
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|
||||
|| helpers::is_typed_dict_class(arguments, checker.semantic())
|
||||
|
||||
@@ -75,7 +75,7 @@ pub(crate) fn mixed_case_variable_in_global_scope(checker: &mut Checker, expr: &
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().stmt();
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E
|
||||
return;
|
||||
}
|
||||
|
||||
let parent = checker.semantic().current_statement();
|
||||
let parent = checker.semantic().stmt();
|
||||
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|
||||
|| helpers::is_typed_dict_assignment(parent, checker.semantic())
|
||||
|| helpers::is_type_var_assignment(parent, checker.semantic())
|
||||
|
||||
@@ -167,7 +167,7 @@ fn is_unused(expr: &Expr, model: &SemanticModel) -> bool {
|
||||
//
|
||||
// print(bar)
|
||||
// ```
|
||||
let scope = model.current_scope();
|
||||
let scope = model.scope();
|
||||
scope
|
||||
.get_all(id)
|
||||
.map(|binding_id| model.binding(binding_id))
|
||||
|
||||
@@ -102,12 +102,12 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
|
||||
range: iterable_range,
|
||||
..
|
||||
}) => {
|
||||
let scope = checker.semantic().current_scope();
|
||||
let scope = checker.semantic().scope();
|
||||
if let Some(binding_id) = scope.get(id) {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
|
||||
if let Some(parent_id) = binding.source {
|
||||
let parent = checker.semantic().statement(parent_id);
|
||||
let parent = checker.semantic().stmts[parent_id];
|
||||
if let Stmt::Assign(ast::StmtAssign { value, .. })
|
||||
| Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value), ..
|
||||
|
||||
@@ -116,10 +116,10 @@ pub(crate) fn lambda_assignment(
|
||||
// rewriting it as a function declaration may break type-checking.
|
||||
// See: https://github.com/astral-sh/ruff/issues/3046
|
||||
// See: https://github.com/astral-sh/ruff/issues/5421
|
||||
if (annotation.is_some() && checker.semantic().current_scope().kind.is_class())
|
||||
if (annotation.is_some() && checker.semantic().scope().kind.is_class())
|
||||
|| checker
|
||||
.semantic()
|
||||
.current_scope()
|
||||
.scope()
|
||||
.get_all(id)
|
||||
.any(|binding_id| checker.semantic().binding(binding_id).kind.is_annotation())
|
||||
{
|
||||
|
||||
@@ -52,13 +52,15 @@ pub(crate) fn type_comparison(checker: &mut Checker, compare: &ast::ExprCompare)
|
||||
}
|
||||
|
||||
// Left-hand side must be, e.g., `type(obj)`.
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = left else {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func, ..
|
||||
}) = left else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
|
||||
if !(id == "type" && checker.semantic().is_builtin("type")) {
|
||||
continue;
|
||||
|
||||
@@ -43,7 +43,9 @@ use crate::registry::{AsRule, Rule};
|
||||
///
|
||||
/// [D211]: https://beta.ruff.rs/docs/rules/blank-line-before-class
|
||||
#[violation]
|
||||
pub struct OneBlankLineBeforeClass;
|
||||
pub struct OneBlankLineBeforeClass {
|
||||
lines: usize,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for OneBlankLineBeforeClass {
|
||||
#[derive_message_formats]
|
||||
@@ -95,7 +97,9 @@ impl AlwaysAutofixableViolation for OneBlankLineBeforeClass {
|
||||
///
|
||||
/// [PEP 257]: https://peps.python.org/pep-0257/
|
||||
#[violation]
|
||||
pub struct OneBlankLineAfterClass;
|
||||
pub struct OneBlankLineAfterClass {
|
||||
lines: usize,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for OneBlankLineAfterClass {
|
||||
#[derive_message_formats]
|
||||
@@ -140,7 +144,9 @@ impl AlwaysAutofixableViolation for OneBlankLineAfterClass {
|
||||
///
|
||||
/// [D203]: https://beta.ruff.rs/docs/rules/one-blank-line-before-class
|
||||
#[violation]
|
||||
pub struct BlankLineBeforeClass;
|
||||
pub struct BlankLineBeforeClass {
|
||||
lines: usize,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for BlankLineBeforeClass {
|
||||
#[derive_message_formats]
|
||||
@@ -164,24 +170,14 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
return;
|
||||
};
|
||||
|
||||
// Special-case: the docstring is on the same line as the class. For example:
|
||||
// ```python
|
||||
// class PhotoMetadata: """Metadata about a photo."""
|
||||
// ```
|
||||
let between_range = TextRange::new(stmt.start(), docstring.start());
|
||||
if !checker.locator().contains_line_break(between_range) {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::OneBlankLineBeforeClass) || checker.enabled(Rule::BlankLineBeforeClass)
|
||||
{
|
||||
let mut lines = UniversalNewlineIterator::with_offset(
|
||||
checker.locator().slice(between_range),
|
||||
between_range.start(),
|
||||
)
|
||||
.rev();
|
||||
let before = checker
|
||||
.locator()
|
||||
.slice(TextRange::new(stmt.start(), docstring.start()));
|
||||
|
||||
let mut blank_lines_before = 0usize;
|
||||
let mut lines = UniversalNewlineIterator::with_offset(before, stmt.start()).rev();
|
||||
let mut blank_lines_start = lines.next().map(|line| line.start()).unwrap_or_default();
|
||||
|
||||
for line in lines {
|
||||
@@ -195,7 +191,12 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
|
||||
if checker.enabled(Rule::BlankLineBeforeClass) {
|
||||
if blank_lines_before != 0 {
|
||||
let mut diagnostic = Diagnostic::new(BlankLineBeforeClass, docstring.range());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
BlankLineBeforeClass {
|
||||
lines: blank_lines_before,
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// Delete the blank line before the class.
|
||||
diagnostic.set_fix(Fix::automatic(Edit::deletion(
|
||||
@@ -208,7 +209,12 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
}
|
||||
if checker.enabled(Rule::OneBlankLineBeforeClass) {
|
||||
if blank_lines_before != 1 {
|
||||
let mut diagnostic = Diagnostic::new(OneBlankLineBeforeClass, docstring.range());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
OneBlankLineBeforeClass {
|
||||
lines: blank_lines_before,
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// Insert one blank line before the class.
|
||||
diagnostic.set_fix(Fix::automatic(Edit::replacement(
|
||||
@@ -223,9 +229,9 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::OneBlankLineAfterClass) {
|
||||
let after_range = TextRange::new(docstring.end(), stmt.end());
|
||||
|
||||
let after = checker.locator().slice(after_range);
|
||||
let after = checker
|
||||
.locator()
|
||||
.slice(TextRange::new(docstring.end(), stmt.end()));
|
||||
|
||||
let all_blank_after = after.universal_newlines().skip(1).all(|line| {
|
||||
line.trim_whitespace().is_empty() || line.trim_whitespace_start().starts_with('#')
|
||||
@@ -234,10 +240,9 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
return;
|
||||
}
|
||||
|
||||
let mut lines = UniversalNewlineIterator::with_offset(after, after_range.start());
|
||||
|
||||
let first_line_start = lines.next().map(|l| l.start()).unwrap_or_default();
|
||||
let mut blank_lines_after = 0usize;
|
||||
let mut lines = UniversalNewlineIterator::with_offset(after, docstring.end());
|
||||
let first_line_start = lines.next().map(|l| l.start()).unwrap_or_default();
|
||||
let mut blank_lines_end = docstring.end();
|
||||
|
||||
for line in lines {
|
||||
@@ -250,7 +255,12 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||
}
|
||||
|
||||
if blank_lines_after != 1 {
|
||||
let mut diagnostic = Diagnostic::new(OneBlankLineAfterClass, docstring.range());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
OneBlankLineAfterClass {
|
||||
lines: blank_lines_after,
|
||||
},
|
||||
docstring.range(),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
// Insert a blank line before the class (replacing any existing lines).
|
||||
diagnostic.set_fix(Fix::automatic(Edit::replacement(
|
||||
|
||||
@@ -72,14 +72,11 @@ pub(crate) fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
||||
return;
|
||||
};
|
||||
|
||||
// Search for occurrences of the function name followed by an open parenthesis (e.g., `foo(` for
|
||||
// a function named `foo`).
|
||||
if first_line
|
||||
.match_indices(name.as_str())
|
||||
.any(|(index, _)| first_line[index + name.len()..].starts_with('('))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(NoSignature, docstring.range()));
|
||||
}
|
||||
if !first_line.contains(&format!("{name}(")) {
|
||||
return;
|
||||
};
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(NoSignature, docstring.range()));
|
||||
}
|
||||
|
||||
@@ -523,7 +523,7 @@ pub(crate) fn not_missing(
|
||||
definition: &Definition,
|
||||
visibility: Visibility,
|
||||
) -> bool {
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_stub() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -272,38 +272,4 @@ D.py:615:5: D400 [*] First line should end with a period
|
||||
617 617 | """
|
||||
618 618 |
|
||||
|
||||
D.py:639:17: D400 [*] First line should end with a period
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
ℹ Suggested fix
|
||||
636 636 | """ This is a docstring that starts with a space.""" # noqa: D210
|
||||
637 637 |
|
||||
638 638 |
|
||||
639 |-class SameLine: """This is a docstring on the same line"""
|
||||
639 |+class SameLine: """This is a docstring on the same line."""
|
||||
640 640 |
|
||||
641 641 | def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
D.py:641:18: D400 [*] First line should end with a period
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
ℹ Suggested fix
|
||||
638 638 |
|
||||
639 639 | class SameLine: """This is a docstring on the same line"""
|
||||
640 640 |
|
||||
641 |-def same_line(): """This is a docstring on the same line"""
|
||||
641 |+def same_line(): """This is a docstring on the same line."""
|
||||
|
||||
|
||||
|
||||
@@ -17,20 +17,4 @@ D.py:636:5: D404 First word of the docstring should not be "This"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|
||||
|
|
||||
|
||||
D.py:639:17: D404 First word of the docstring should not be "This"
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
|
|
||||
|
||||
D.py:641:18: D404 First word of the docstring should not be "This"
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -254,38 +254,4 @@ D.py:615:5: D415 [*] First line should end with a period, question mark, or excl
|
||||
617 617 | """
|
||||
618 618 |
|
||||
|
||||
D.py:639:17: D415 [*] First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
ℹ Suggested fix
|
||||
636 636 | """ This is a docstring that starts with a space.""" # noqa: D210
|
||||
637 637 |
|
||||
638 638 |
|
||||
639 |-class SameLine: """This is a docstring on the same line"""
|
||||
639 |+class SameLine: """This is a docstring on the same line."""
|
||||
640 640 |
|
||||
641 641 | def same_line(): """This is a docstring on the same line"""
|
||||
|
||||
D.py:641:18: D415 [*] First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
639 | class SameLine: """This is a docstring on the same line"""
|
||||
640 |
|
||||
641 | def same_line(): """This is a docstring on the same line"""
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
ℹ Suggested fix
|
||||
638 638 |
|
||||
639 639 | class SameLine: """This is a docstring on the same line"""
|
||||
640 640 |
|
||||
641 |-def same_line(): """This is a docstring on the same line"""
|
||||
641 |+def same_line(): """This is a docstring on the same line."""
|
||||
|
||||
|
||||
|
||||
@@ -12,14 +12,11 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
|
||||
use test_case::test_case;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::AsMode;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
@@ -507,9 +504,8 @@ mod tests {
|
||||
/// Note that all tests marked with `#[ignore]` should be considered TODOs.
|
||||
fn flakes(contents: &str, expected: &[Rule]) {
|
||||
let contents = dedent(contents);
|
||||
let source_type = PySourceType::default();
|
||||
let settings = Settings::for_rules(Linter::Pyflakes.rules());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents, source_type.as_mode());
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(&contents);
|
||||
let locator = Locator::new(&contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
let indexer = Indexer::from_tokens(&tokens, &locator);
|
||||
@@ -533,7 +529,6 @@ mod tests {
|
||||
&settings,
|
||||
flags::Noqa::Enabled,
|
||||
None,
|
||||
source_type,
|
||||
);
|
||||
diagnostics.sort_by_key(Diagnostic::start);
|
||||
let actual = diagnostics
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user